#@+leo-ver=5-thin
#@+node:ekr.20170302123956.1: * @file ../doc/leoAttic.txt
# This is Leo's final resting place for dead code.
# Much easier to access than a git repo.

#@@language python
#@@killbeautify
#@+all
#@+node:ekr.20190412154346.1: ** Unused commands
#@+node:ekr.20150514063305.107: *3* debug.enable/disableGcTrace
@cmd('gc-trace-disable')
def disableGcTrace(self, event=None):
    '''Enable tracing of Python's Garbage Collector.'''
    g.trace_gc = False

@cmd('gc-trace-enable')
def enableGcTrace(self, event=None):
    '''Disable tracing of Python's Garbage Collector.'''
    g.trace_gc = True
    g.enable_gc_debug()
    if g.trace_gc_verbose:
        g.blue('enabled verbose gc stats')
    else:
        g.blue('enabled brief gc stats')
#@+node:ekr.20101103093942.5941: *3* @g.command(head-to-prev-node)
@g.command('head-to-prev-node')
def headToPrevNode(event):
    '''Move the code preceding a def to end of previous node.'''
    c = event.get('c')
    if not c: return
    p = c.p
    try:
        import leo.plugins.importers.python as python
    except ImportError:
        return
    scanner = python.Py_Importer(c.importCommands)
    kind, i, junk = scanner.find_class(p)
    p2 = p.back()
    if p2 and kind in ('class', 'def') and i > 0:
        u = c.undoer; undoType = 'move-head-to-prev'
        head = p.b[: i].rstrip()
        u.beforeChangeGroup(p, undoType)
        b = u.beforeChangeNodeContents(p)
        p.b = p.b[i:]
        u.afterChangeNodeContents(p, undoType, b)
        if head:
            b2 = u.beforeChangeNodeContents(p2)
            p2.b = p2.b.rstrip() + '\n\n' + head + '\n'
            u.afterChangeNodeContents(p2, undoType, b2)
        u.afterChangeGroup(p, undoType)
        c.selectPosition(p2)
#@+node:ekr.20101103093942.5943: *3* @g.command(tail-to-next-node)
@g.command('tail-to-next-node')
def tailToNextNode(event=None):
    '''Move the code following a def to start of next node.'''
    c = event.get('c')
    if not c: return
    p = c.p
    try:
        import leo.plugins.importers.python as python
    except ImportError:
        return
    scanner = python.Py_Importer(c.importCommands)
    kind, junk, j = scanner.find_class(p)
    p2 = p.next()
    if p2 and kind in ('class', 'def') and j < len(p.b):
        u = c.undoer; undoType = 'move-tail-to-next'
        tail = p.b[j:].rstrip()
        u.beforeChangeGroup(p, undoType)
        b = u.beforeChangeNodeContents(p)
        p.b = p.b[: j]
        u.afterChangeNodeContents(p, undoType, b)
        if tail:
            b2 = u.beforeChangeNodeContents(p2)
            p2.b = tail + '\n\n' + p2.b
            u.afterChangeNodeContents(p2, undoType, b2)
        u.afterChangeGroup(p, undoType)
        c.selectPosition(p2)
#@+node:ekr.20070919133659.1: *3* fc.checkLeoFile
@cmd('check-leo-file')
def checkLeoFile(self, event=None):
    '''The check-leo-file command.'''
    fc = self; c = fc.c; p = c.p
    # 
    # Put the body (minus the @nocolor) into the file buffer.
    s = p.b
    tag = '@nocolor\n'
    if s.startswith(tag): s = s[len(tag):]
    # Do a trial read.
    self.checking = True
    self.initReadIvars()
    c.loading = True # disable c.changed
    try:
        try:
            theFile = g.app.loadManager.openLeoOrZipFile(c.mFileName)
            gnxDict = self.gnxDict
            self.gnxDict = {} # Always allocate new vnodes.
            try:
                s = theFile.read()
                FastRead(c, self.gnxDict).readFile(s=s)
            finally:
                self.gnxDict = gnxDict
            g.blue('check-leo-file passed')
        except Exception:
            junk, message, junk = sys.exc_info()
            g.error('check-leo-file failed:', g.toUnicode(message))
    finally:
        self.checking = False
        c.loading = False # reenable c.changed
#@+node:ekr.20180504182541.1: *3* efc.getDiffBranch (not used)
@cmd('git-diff-branch')
def gitDiffBranch(self, event):
    '''Prompt for a branch name, then diff one file against it.'''
    c = self.c
    roots = g.findRootsWithPredicate(c, c.p)
        # Looks up and down the tree.
    if not roots:
        g.es_print('no .py files found')
        return
    gdc = GitDiffController(self.c, 'HEAD')
    
    def callback(args, *extra, **kwargs):
        if g._assert(len(args) == 1):
            for p in roots:
                gdc.diff_branch(branch=args[0], fn=p.anyAtFileNodeName())
                
    c.interactive(callback, event, prompts=['Branch name: '])
#@+node:ekr.20190422174221.1: ** Unused settings
# These were in leoSettings.leo
#@+node:ekr.20190422174235.1: *3* leowapp plugin
#@+node:ekr.20190422174235.2: *4* @int leowapp-port = 8100
#@+node:ekr.20190422174235.3: *4* @string leowapp-ip = 127.0.0.1
#@+node:ekr.20190422174235.4: *4* stylesheet
@language css
#@+node:ekr.20190422174235.5: *5* @data leowapp-stylesheet
/*
    The main .css stylesheet for the mod_http.py plugin.
    This stylesheet allocates fix-sized areas for the outline and body panes.
*/
div.container {
    position: absolute;
}

@others
#@+node:ekr.20190422174235.6: *6* borders
.borderclass{
    border-color: #C1E0FF;
    border-width: 1px;
    border-style: solid;
}
.unborderclass{
    border-color: white;
    border-width: 1px;
    border-style: solid;
}
#@+node:ekr.20190422174235.7: *6* panes
/*
    In page order:
    outline: top: 10px
    body:    top: 310px
    log:     top: 510px
    mini:    top: 710px

    Fixed height/width creates scrollbars.
*/
div.outlinepane {
    background: #ffffec; /* Leo yellow */
    top: 10px;
    height: 300px;
    /* ---------- */
    /* position: absolute: */
    width: 1000px;
    left: 10px;
    bottom: 0px;
    overflow: scroll;
    line-height: 0.8;
    resize: none;
}
div.bodypane {
    background: yellow;
    top: 310px;
    height: 200px;
    /* ---------- */
    overflow: scroll;
    position: absolute;
    resize: none;
    /* ---------- */
    bottom: 0px;
    left: 10px;
    width: 1000px;
}
div.logpane {
    background: orange;
    top: 510px;
    height: 200px;
    /* ---------- */
    overflow: scroll;
    position: absolute;
    resize: none;
    /* ---------- */
    bottom: 0px;
    width: 1000px;
    left: 10px;
}
div.minibufferpane {
    background: red;
    top: 710px;
    height: 50px;
    /* ---------- */
    position: absolute;
    overflow: scroll;
    resize: none;
    /* ---------- */
    bottom: 0px;
    left: 10px;
    width: 1000px;
}
#@+node:ekr.20190422174235.8: *6* text
code {
    font-family: "Courier New", Courier, monospace;
    font-size: 12pt;
}
h1 {
    /* font-size: 16pt; */
    /* color: red; */
}
#@+node:ekr.20190422174235.9: *6* tree components
div.headline {
    color: blue;
    font-size: 16pt;
    font-style: normal;
    font-weight: normal;
    /* Prevent false clicks. */
    margin-top: 2px;
    margin-bottom: 2px;
    padding: 4px;
}
div.node {
    position: relative;
    left: 20px;
}
#@+node:ekr.20190422174235.10: *6* tree icons
/* Indicator icons...

    Alas, the following does not work::

        node::before {
            content: url(attr(icon_url)) " " attr(expand) " ";
        }
*/
div.headline[icon="00"]::before {
    content: url("http://leoeditor.com/box00.GIF") " " attr(expand) " ";
}
div.headline[icon="01"]::before {
    content: url("http://leoeditor.com/box01.GIF") " " attr(expand) " ";
}
div.headline[icon="02"]::before {
    content: url("http://leoeditor.com/box02.GIF") " " attr(expand) " ";
}
div.headline[icon="03"]::before {
    content: url("http://leoeditor.com/box03.GIF") " " attr(expand) " ";
}
div.headline[icon="04"]::before {
    content: url("http://leoeditor.com/box04.GIF") " " attr(expand) " ";
}
div.headline[icon="05"]::before {
    content: url("http://leoeditor.com/box05.GIF") " " attr(expand) " ";
}
div.headline[icon="06"]::before {
    content: url("http://leoeditor.com/box06.GIF") " " attr(expand) " ";
}
div.headline[icon="07"]::before {
    content: url("http://leoeditor.com/box07.GIF") " " attr(expand) " ";
}
div.headline[icon="08"]::before {
    content: url("http://leoeditor.com/box08.GIF") " " attr(expand) " ";
}
div.headline[icon="09"]::before {
    content: url("http://leoeditor.com/box09.GIF") " " attr(expand) " ";
}
div.headline[icon="10"]::before {
    content: url("http://leoeditor.com/box10.GIF") " " attr(expand) " ";
}
div.headline[icon="11"]::before {
    content: url("http://leoeditor.com/box11.GIF") " " attr(expand) " ";
}
div.headline[icon="12"]::before {
    content: url("http://leoeditor.com/box12.GIF") " " attr(expand) " ";
}
div.headline[icon="13"]::before {
    content: url("http://leoeditor.com/box13.GIF") " " attr(expand) " ";
}
div.headline[icon="14"]::before {
    content: url("http://leoeditor.com/box14.GIF") " " attr(expand) " ";
}
div.headline[icon="15"]::before {
    content: url("http://leoeditor.com/box15.GIF") " " attr(expand) " ";
}
#@+node:ekr.20190412154439.1: ** Abandoned projects
#@+node:ekr.20170203080350.1: *3* Abandoned #396: Show images in Leo's body pane
https://github.com/leo-editor/leo-editor/issues/396

#@+node:ekr.20170302151109.1: *4* ** Notes
@language rest
@wrap

Unicode 'object replacement character': u+FFFC

QTextDocument may be helpful: http://doc.qt.io/qt-5/qtextdocument.html
See QTextDocument.MetaInformation: http://doc.qt.io/qt-5/qtextdocument.html#MetaInformation-enum

http://stackoverflow.com/questions/3254652/
several-ways-of-placing-an-image-in-a-qtextedit

http://doc.qt.io/qt-5/qtextdocument.html#resource

QVariant QTextDocument::resource(int type, const QUrl &name) const

Returns data of the specified type from the resource with the given name.

This function is called by the rich text engine to request data that isn't directly stored by QTextDocument, but still associated with it. For example, images are referenced indirectly by the name attribute of a QTextImageFormat object.

Resources are cached internally in the document. If a resource can not be found in the cache, loadResource is called to try to load the resource. loadResource should then use addResource to add the resource to the cache.
#@+node:ekr.20170203105538.1: *4* Test inserting picture (new)
# https://github.com/leo-editor/leo-editor/issues/396
g.cls()
images = [i for i in range(len(p.b)) if ord(p.b[i]) > 128]
if images:
    print('images at', images)
else:
    table = (
        'application-x-leo-outline.png',
        'LeoDoc.ico',
    )    
    for image in table:
        path = g.os_path_finalize_join(g.app.loadDir, '..', 'Icons', image)
        assert g.os_path_exists(path), repr(path)
        c.frame.body.wrapper.setInsertPoint(len(p.b))
        if 0:
            format = QtGui.QTextImageFormat()
            format.setName(path)
            cursor = cursor = body.widget.textCursor()
            cursor.insertImage(format)
        c.frame.body.widget.insertHtml('<img src="%s">' % path)
            # style="width:40px;height:80px;"
    for i, ch in enumerate(p.b):
        if ord(ch) > 128: print('new', i, ord(ch))
#
#￼￼
#@+node:ekr.20170204110006.1: *4* test3 insert an image
g.cls()
from leo.core.leoQt import QtGui
body = c.frame.body
table = ('box01.bmp','box02.bmp','box03.bmp',)
d = g.app.permanentScriptDict
images = d.get('images', [])
for image in table:
    path = g.os_path_finalize_join(g.app.loadDir, '..', 'Icons', image)
    assert g.os_path_exists(path), repr(path)
    image = QtGui.QImage(path)
    images.append(image)
    body.wrapper.setInsertPoint(len(p.b))
    cursor = body.widget.textCursor()
    cursor.insertImage(image)
for i, ch in enumerate(p.b):
    if ord(ch) > 128: print('new', i, ord(ch))
d ['images'] = images
#
#
#@+node:ekr.20170204140521.1: *4* clear g.app.permanentScriptDict
g.printDict(g.app.permanentScriptDict)
g.app.permanentScriptDict = {}
#@+node:ekr.20170204135338.1: *4* @@button show-images
from leo.core.leoQt import QtCore
d = g.app.permanentScriptDict
name_index = d.get('name_index', 0)
names = ['leo_image%s' % (i) for i in range(name_index)]
# print('image names', names)
widget = c.frame.body.widget
doc = widget.document()
for i, name in enumerate(names):
    image = doc.resource(doc.ImageResource, QtCore.QUrl(name))
    print(name, image)
#@+node:ekr.20170204105958.1: *4* test2 insert an image
g.cls()
from leo.core.leoQt import QtGui
body = c.frame.body
table = (
    'application-x-leo-outline.png',
    'LeoDoc.ico',
)
g.app.permanentScriptDict = {}
d = g.app.permanentScriptDict
images = d.get('images', [])
cursors = d.get('cursors', [])
name_index = d.get('name_index', 0)
for image in table:
    path = g.os_path_finalize_join(g.app.loadDir, '..', 'Icons', image)
    assert g.os_path_exists(path), repr(path)
    image = QtGui.QImage(path)
    print(image.format().name())
    images.append(image)
    body.wrapper.setInsertPoint(len(p.b))
    cursor = body.widget.textCursor()
    #name = 'leo_image%s' % name_index
    #name_index += 1
    cursor.insertImage(image)
    cursors.append(cursor)
for i, ch in enumerate(p.b):
    if ord(ch) > 128: print('new', i, ord(ch))
d ['images'] = images
d ['cursors'] = cursors
d ['name_index'] = name_index
g.app.permanentScriptDict = d
g.printDict(d)
#
#￼￼￼
#￼
#@+node:ekr.20180328065332.1: *3* Check conventions stuff
#@+node:ekr.20171208042251.1: *4* @@button check-conventions (no longer used)
g.cls()
if c.changed: c.save()

import imp
import leo.core.leoCheck as leoCheck
imp.reload(leoCheck)

do_all = True
do_string = True

fails = []
    # All of Leo's core files pass!
fn = g.os_path_finalize_join(g.app.loadDir, '..', 'core', 'leoTest.py')
<< define s >>
<< old tests >>
if do_all:
    utils = leoCheck.ProjectUtils()
    aList = utils.project_files('leo', force_all=False)
    # g.printList(aList)
    for fn in aList:
        sfn = g.shortFileName(fn)
        if sfn in fails:
            print('===== skipping', sfn)
        else:
            print('==== fn', sfn)
            leoCheck.ConventionChecker(c).check(fn=fn)
elif do_string: # Test string s.
    leoCheck.ConventionChecker(c).check(s=s)
else: # Test an actual file.
    leoCheck.ConventionChecker(c).check(fn=fn)
#@+node:ekr.20171208105236.1: *5* << define s >>
s = '''\
class T:
    
    def __init__(self, tempNode):
        self.tempNode = tempNode.copy()
    
    def setUp(self):
        tempNode = self.tempNode
        while tempNode.firstChild():
            tempNode.firstChild().doDelete()
'''

s_ok2 = '''
class Context(object):
    def __init__ (self, parent_context):
        self.parent_context = parent_context
        if parent_context:
            parent_context.inner_contexts_list.append(self)
'''

s_ok= '''
class TC:
    def __init__(self, c):
        c.tc = self
    def add_tag(self, p):
        print(p.v) # AttributeError if p is a vnode.

class Test:
    def __init__(self,c):
        self.c = c
        self.tc = self.c.tc
    def add_tag(self):
        p = self.c.p
        self.tc.add_tag(p.v) # WRONG: arg should be p.
'''

#@+node:ekr.20171210062719.1: *5* << old tests >>
s_passes_1 = '''\
class C1:
        
    def f1(self, p):
        print(p.v)
        
    def f2(self, p):
        self.f1(p.v) # WRONG

'''


s_1 = '''\
class C1:

    def __init__(self, c):
        self.c = c
        c.theTagController = self
        
    def add_tag(self, p):
        pass

class C2:

    def oops(self, p):
        c.tagController.add_tag(p.v,tag)
            # WRONG: should be p.

'''


s_2 = '''\
class TagController:

    def __init__(self, c):
        self.c = c
        c.theTagController = self

    def add_tag(self, p, tag):
        # Will fail if p is a vnode
        tags = set(p.v.u.get('__node_tags', set([])))

class LeoTagWidget(QtWidgets.QWidget):

    def __init__(self,c,parent=None):
        self.c = c
        self.tc = self.c.theTagController

    def add_tag(self, event=None):
        p = self.c.p
        self.tc.add_tag(p.v,tag) # WRONG: should be p.

'''
#@+node:ekr.20160109150703.1: *4* class Stats (old & stupid, from leoCheck.py)
class Stats(object):
    '''A class containing global statistics & other data'''
    @others
#@+node:ekr.20160109150703.2: *5*  sd.ctor
def __init__ (self):

    # Files...
    # self.completed_files = [] # Files handled by do_files.
    # self.failed_files = [] # Files that could not be opened.
    # self.files_list = [] # Files given by user or by import statements.
    # self.module_names = [] # Module names corresponding to file names.

    # Contexts.
    # self.context_list = {}
        # Keys are fully qualified context names; values are contexts.
    # self.modules_dict = {}
        # Keys are full file names; values are ModuleContext's.

    # Statistics...
    # self.n_chains = 0
    self.n_contexts = 0
    # self.n_errors = 0
    self.n_lambdas = 0
    self.n_modules = 0
    # self.n_relinked_pointers = 0
    # self.n_resolvable_names = 0
    # self.n_resolved_contexts = 0
    # self.n_relinked_names = 0

    # Names...
    self.n_attributes = 0
    self.n_expressions = 0
    self.n_ivars = 0
    self.n_names = 0        # Number of symbol table entries.
    self.n_del_names = 0
    self.n_load_names = 0
    self.n_param_names = 0
    self.n_param_refs = 0
    self.n_store_names = 0

    # Statements...
    self.n_assignments = 0
    self.n_calls = 0
    self.n_classes = 0
    self.n_defs = 0
    self.n_fors = 0
    self.n_globals = 0
    self.n_imports = 0
    self.n_lambdas = 0
    self.n_list_comps = 0
    self.n_returns = 0
    self.n_withs = 0

    # Times...
    self.parse_time = 0.0
    self.pass1_time = 0.0
    self.pass2_time = 0.0
    self.total_time = 0.0
#@+node:ekr.20160109150703.6: *5* sd.print_times
def print_times (self):

    sd = self
    times = (
        'parse_time',
        'pass1_time',
        # 'pass2_time', # the resolve_names pass is no longer used.
        'total_time',
    )
    max_n = 5
    for s in times:
        max_n = max(max_n,len(s))
    print('\nScan times...\n')
    for s in times:
        pad = ' ' * (max_n - len(s))
        print('%s%s: %2.2f' % (pad,s,getattr(sd,s)))
    print('')
#@+node:ekr.20160109150703.7: *5* sd.print_stats
def print_stats (self):

    sd = self
    table = (
        '*', 'errors',

        '*Contexts',
        'classes','contexts','defs','modules',

        '*Statements',
        'assignments','calls','fors','globals','imports',
        'lambdas','list_comps','returns','withs',

        '*Names',
        'attributes','del_names','load_names','names',
        'param_names','param_refs','store_names',
        #'resolvable_names','relinked_names','relinked_pointers',
        # 'ivars',
        # 'resolved_contexts',
    )
    max_n = 5
    for s in table:
        max_n = max(max_n,len(s))
    print('\nStatistics...\n')
    for s in table:
        var = 'n_%s' % s
        pad = ' ' * (max_n - len(s))
        if s.startswith('*'):
            if s[1:].strip():
                print('\n%s\n' % s[1:])
            else:
                pass # print('')
        else:
            pad = ' ' * (max_n - len(s))
            print('%s%s: %s' % (pad,s,getattr(sd,var)))
    print('')
#@+node:ekr.20171211054600.1: *4* OLD checkConventions (leoCheck.py)
def checkConventions(c):
    '''
    A stand-alone version of the @button node that tested the
    ConventionChecker class.
    
    The check-conventions command in checkerCommands.py saves c and reloads
    the leoCheck module before calling this function.
    '''
    g.cls()
    kind = 'all'
    project_name = 'leo'  # 'coverage', 'leo', 'lib2to3', 'pylint', 'rope'
    assert kind in ('all', 'file', 'production', 'string'), repr(kind)
    fn = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins', 'qt_tree.py')
    report_stats = True # and kind != 'production'
    trace_fn = True
    trace_skipped = False
    fails_dict = {
        'coverage': ['cmdline.py',],
        'lib2to3': ['fixer_util.py', 'fix_dict.py', 'patcomp.py', 'refactor.py'],
        'leo': [], # All of Leo's core files pass.
        'pylint': [
            'base.py', 'classes.py', 'format.py',
            'logging.py', 'python3.py', 'stdlib.py', 
            'docparams.py', 'lint.py',
        ],
        'rope': ['objectinfo.py', 'objectdb.py', 'runmod.py',],
    }
    fails = fails_dict.get(project_name, [])
    << define s >>
    s = g.adjustTripleString(s, c.tab_width)
    << old tests >>
    stats = Stats()
    if kind == 'production':
        for p in g.findRootsWithPredicate(c, c.p, predicate=None):
            x = ConventionChecker(c, stats)
            x.check(fn=g.fullPath(c, p), trace_fn=trace_fn)
    elif kind == 'all':
        utils = ProjectUtils()
        aList = utils.project_files(project_name, force_all=False)
        if aList:
            t1 = time.clock()
            for fn in aList:
                sfn = g.shortFileName(fn)
                if sfn in fails or fn in fails:
                    if trace_skipped: print('===== skipping', sfn)
                else:
                    ConventionChecker(c, stats).check(fn=fn, trace_fn=trace_fn)
            t2 = time.clock()
            print('%s files in %4.2f sec.' % (len(aList), (t2-t1)))
        else:
            print('no files for project: %s' % (project_name))
    elif kind == 'string':
        ConventionChecker(c, stats).check(s=s)
    else:
        assert kind == 'file', repr(kind)
        ConventionChecker(c, stats).check(fn=fn)
    if report_stats:
        stats.report()
#@+node:ekr.20171211054736.2: *5* << define s >>
s = '''\
class T:
    
    def __init__(self, tempNode):
        self.tempNode = tempNode.copy()
    
    def setUp(self):
        tempNode = self.tempNode
        while tempNode.firstChild():
            tempNode.firstChild().doDelete()
'''

s_ok2 = '''
class Context(object):
    def __init__ (self, parent_context):
        self.parent_context = parent_context
        if parent_context:
            parent_context.inner_contexts_list.append(self)
'''
assert s_ok2

s_ok= '''
class TC:
    def __init__(self, c):
        c.tc = self
    def add_tag(self, p):
        print(p.v) # AttributeError if p is a vnode.

class Test:
    def __init__(self,c):
        self.c = c
        self.tc = self.c.tc
    def add_tag(self):
        p = self.c.p
        self.tc.add_tag(p.v) # WRONG: arg should be p.
'''
assert s_ok

#@+node:ekr.20171211054736.3: *5* << old tests >>
s_passes_1 = '''\
class C1:
        
    def f1(self, p):
        print(p.v)
        
    def f2(self, p):
        self.f1(p.v) # WRONG

'''
assert s_passes_1

s_1 = '''\
class C1:

    def __init__(self, c):
        self.c = c
        c.theTagController = self
        
    def add_tag(self, p):
        pass

class C2:

    def oops(self, p):
        c.tagController.add_tag(p.v,tag)
            # WRONG: should be p.

'''
assert s_1


s_2 = '''\
class TagController:

    def __init__(self, c):
        self.c = c
        c.theTagController = self

    def add_tag(self, p, tag):
        # Will fail if p is a vnode
        tags = set(p.v.u.get('__node_tags', set([])))

class LeoTagWidget(QtWidgets.QWidget):

    def __init__(self,c,parent=None):
        self.c = c
        self.tc = self.c.theTagController

    def add_tag(self, event=None):
        p = self.c.p
        self.tc.add_tag(p.v,tag) # WRONG: should be p.

'''
assert s_2
#@+node:ekr.20150312225028.29: *3* leoViews project
This was a major project, now abandoned.
#@+node:ekr.20150312225028.31: *4* class OrganizerData
class OrganizerData:
    '''A class containing all data for a particular organizer node.'''
    def __init__ (self,h,unl,unls):
        self.anchor = None # The anchor position of this od node.
        self.children = [] # The direct child od nodes of this od node.
        self.closed = False # True: this od node no longer accepts new child od nodes.
        self.drop = True # Drop the unl for this od node when associating positions with unls.
        self.descendants = None # The descendant od nodes of this od node.
        self.exists = False # True: this od was created by @existing-organizer:
        self.h = h # The headline of this od node.
        self.moved = False # True: the od node has been moved to a global move list.
        self.opened = False # True: the od node has been opened.
        self.organized_nodes = [] # The list of positions organized by this od node.
        self.parent_od = None # The parent od node of this od node. (None is valid.)
        self.p = None # The position of this od node.
        self.parent = None # The original parent position of all nodes organized by this od node.
            # If parent_od is None, this will be the parent position of the od node.
        self.source_unl = None # The unl of self.parent.
        self.unl = unl # The unl of this od node.
        self.unls = unls # The unls contained in this od node.
        self.visited = False # True: demote_helper has already handled this od node.
    def __repr__(self):
        return 'OrganizerData: %s' % (self.h or '<no headline>')
    __str__ = __repr__
#@+node:ekr.20150312225028.32: *4* class ViewController
class ViewController:
    << docstring >>
    @others
#@+node:ekr.20150312225028.33: *5*  << docstring >> (class ViewController)
'''
A class to handle @views trees and related operations.
Such trees have the following structure:

- @views
  - @auto-view <unl of @auto node>
    - @organizers
      - @organizer <headline>
    - @clones
    
The body text of @organizer and @clones consists of unl's, one per line.
'''
#@+node:ekr.20150312225028.34: *5*  vc.ctor & vc.init
def __init__ (self,c):
    '''Ctor for ViewController class.'''
    self.c = c
    self.headline_ivar = '_imported_headline'
    self.init()
    
def init(self):
    '''
    Init all ivars of this class.
    Unit tests may call this method to ensure that this class is re-inited properly.
    '''
    self.all_ods = []
        # List of all od nodes.
    self.anchors_d = {}
        # Keys are anchoring positions, values are sorted lists of ods.
    self.anchor_offset_d = {}
        # Keys are anchoring positions, values are ints.
    self.existing_ods = []
        # List of od instances corresponding to @existing-organizer: nodes.
    self.global_bare_organizer_node_list = []
        # List of organizers that have no parent organizer node.
        # This list excludes existing organizer nodes.
    self.headlines_dict = {}
        # Keys are vnodes; values are list of child headlines.
    self.imported_organizers_list = []
        # The list of nodes that have children on entry, such as class nodes.
    self.n_nodes_scanned = 0
        # Number of nodes scanned by demote.
    self.organizer_ods = []
        # List of od instances corresponding to @organizer: nodes.
    self.organizer_unls = []
        # The list of od.unl for all od instances in self.organizer_ods.
    self.root = None
        # The position of the @auto node.
    self.pending = []
        # The list of nodes pending to be added to an organizer.
    self.stack = []
        # The stack containing real and virtual parent nodes during the main loop.
    self.temp_node = None
        # The parent position of all holding cells.
    self.trail_write_1 = None
        # The trial write on entry.
    self.views_node = None
        # The position of the @views node.
    self.work_list = []
        # A gloal list of (parent,child) tuples for all nodes that are
        # to be moved to **non-existing** organizer nodes.
        # **Important**: Nodes are moved in the order they appear in this list:
        # the tuples contain no childIndex component!
        # This list is the "backbone" of this class:
        # - The front end (demote and its helpers) adds items to this list.
        # - The back end (move_nodes and its helpers) moves nodes using this list.
#@+node:ekr.20150312225028.35: *5* vc.Entry points
#@+node:ekr.20150312225028.36: *6* vc.convert_at_file_to_at_auto
def convert_at_file_to_at_auto(self,root):
    # Define class ConvertController.
    @others
    vc = self
    c = vc.c
    if root.isAtFileNode():
        ConvertController(c,root).run()
    else:
        g.es_print('not an @file node:',root.h)
#@+node:ekr.20150312225028.37: *7* class ConvertController
class ConvertController:
    def __init__ (self,c,p):
        self.c = c
        # self.ic = c.importCommands
        self.vc = c.viewController
        self.root = p.copy()
    @others
#@+node:ekr.20150312225028.38: *8* cc.delete_at_auto_view_nodes
def delete_at_auto_view_nodes(self,root):
    '''Delete all @auto-view nodes pertaining to root.'''
    cc = self
    vc = cc.vc
    while True:
        p = vc.has_at_auto_view_node(root)
        if not p: break
        p.doDelete()
#@+node:ekr.20150312225028.39: *8* cc.import_from_string
def import_from_string(self,s):
    '''Import from s into a temp outline.'''
    cc = self # (ConvertController)
    c = cc.c
    ic = c.importCommands
    root = cc.root
    language = g.scanForAtLanguage(c,root) 
    ext = '.'+g.app.language_extension_dict.get(language)
    scanner = ic.scanner_for_ext(ext)
    # g.trace(language,ext,scanner.__name__)
    p = root.insertAfter()
    ok = scanner(atAuto=True,parent=p,s=s)
    p.h = root.h.replace('@file','@auto' if ok else '@@auto')
    return ok,p
#@+node:ekr.20150312225028.40: *8* cc.run
def run(self):
    '''Convert an @file tree to @auto tree.'''
    trace = True and not g.unitTesting
    trace_s = False
    cc = self
    c = cc.c
    root,vc = cc.root,c.viewController
    # set the headline_ivar for all vnodes.
    t1 = time.clock()
    cc.set_expected_imported_headlines(root)
    t2 = time.clock()
    # Delete all previous @auto-view nodes for this tree.
    cc.delete_at_auto_view_nodes(root)
    t3 = time.clock()
    # Ensure that all nodes of the tree are regularized.
    ok = vc.prepass(root)
    t4 = time.clock()
    if not ok:
        g.es_print('Can not convert',root.h,color='red')
        if trace: g.trace(
            '\n  set_expected_imported_headlines: %4.2f sec' % (t2-t1),
            # '\n  delete_at_auto_view_nodes:     %4.2f sec' % (t3-t2),
            '\n  prepass:                         %4.2f sec' % (t4-t3),
            '\n  total:                           %4.2f sec' % (t4-t1))
        return
    # Create the appropriate @auto-view node.
    at_auto_view = vc.update_before_write_at_auto_file(root)
    t5 = time.clock()
    # Write the @file node as if it were an @auto node.
    s = cc.strip_sentinels()
    t6 = time.clock()
    if trace and trace_s:
        g.trace('source file...\n',s)
    # Import the @auto string.
    ok,p = cc.import_from_string(s)
    t7 = time.clock()
    if ok:
        # Change at_auto_view.b so it matches p.gnx.
        at_auto_view.b = vc.at_auto_view_body(p)
        # Recreate the organizer nodes, headlines, etc.
        ok = vc.update_after_read_at_auto_file(p)
        t8 = time.clock()
        if not ok:
            p.h = '@@' + p.h
            g.trace('restoring original @auto file')
            ok,p = cc.import_from_string(s)
            if ok:
                p.h = '@@' + p.h + ' (restored)'
                if p.next():
                    p.moveAfter(p.next())
        t9 = time.clock()
    else:
        t8 = t9 = time.clock()
    if trace: g.trace(
        '\n  set_expected_imported_headlines: %4.2f sec' % (t2-t1),
        # '\n  delete_at_auto_view_nodes:     %4.2f sec' % (t3-t2),
        '\n  prepass:                         %4.2f sec' % (t4-t3),
        '\n  update_before_write_at_auto_file:%4.2f sec' % (t5-t4),
        '\n  strip_sentinels:                 %4.2f sec' % (t6-t5),
        '\n  import_from_string:              %4.2f sec' % (t7-t6),
        '\n  update_after_read_at_auto_file   %4.2f sec' % (t8-t7),
        '\n  import_from_string (restore)     %4.2f sec' % (t9-t8),
        '\n  total:                           %4.2f sec' % (t9-t1))
    if p:
        c.selectPosition(p)
    c.redraw()
#@+node:ekr.20150312225028.41: *8* cc.set_expected_imported_headlines
def set_expected_imported_headlines(self,root):
    '''Set the headline_ivar for all vnodes.'''
    trace = False and not g.unitTesting
    cc = self
    c = cc.c
    ic = cc.c.importCommands
    language = g.scanForAtLanguage(c,root) 
    ext = '.'+g.app.language_extension_dict.get(language)
    aClass = ic.classDispatchDict.get(ext)
    scanner = aClass(importCommands=ic,atAuto=True)
    # Duplicate the fn logic from ic.createOutline.
    theDir = g.setDefaultDirectory(c,root,importing=True)
    fn = c.os_path_finalize_join(theDir,root.h)
    fn = root.h.replace('\\','/')
    junk,fn = g.os_path_split(fn)
    fn,junk = g.os_path_splitext(fn)
    if aClass and hasattr(scanner,'headlineForNode'):
        ivar = cc.vc.headline_ivar
        for p in root.subtree():
            if not hasattr(p.v,ivar):
                h = scanner.headlineForNode(fn,p)
                setattr(p.v,ivar,h)
                if trace and h != p.h:
                    g.trace('==>',h) # p.h,'==>',h
#@+node:ekr.20150312225028.42: *8* cc.strip_sentinels
def strip_sentinels(self):
    '''Write the file to a string without headlines or sentinels.'''
    trace = False and not g.unitTesting
    cc = self
    at = cc.c.atFileCommands
    # ok = at.writeOneAtAutoNode(cc.root,
        # toString=True,force=True,trialWrite=True)
    at.errors = 0
    at.write(cc.root,
        kind = '@file',
        nosentinels = True,
        perfectImportFlag = False,
        scriptWrite = False,
        thinFile = True,
        toString = True)
    ok = at.errors == 0
    s = at.stringOutput
    if trace: g.trace('ok:',ok,'s:...\n'+s)
    return s
#@+node:ekr.20150312225028.43: *6* vc.pack & helper
def pack(self):
    '''
    Undoably convert c.p to a packed @view node, replacing all cloned
    children of c.p by unl lines in c.p.b.
    '''
    vc = self
    c,u = vc.c,vc.c.undoer
    vc.init()
    changed = False
    root = c.p
    # Create an undo group to handle changes to root and @views nodes.
    # Important: creating the @views node does *not* invalidate any positions.'''
    u.beforeChangeGroup(root,'view-pack')
    if not vc.has_at_views_node():
        changed = True
        bunch = u.beforeInsertNode(c.rootPosition())
        views = vc.find_at_views_node()
            # Creates the @views node as the *last* top-level node
            # so that no positions become invalid as a result.
        u.afterInsertNode(views,'create-views-node',bunch)
    # Prepend @view if need.
    if not root.h.strip().startswith('@'):
        changed = True
        bunch = u.beforeChangeNodeContents(root)
        root.h = '@view ' + root.h.strip()
        u.afterChangeNodeContents(root,'view-pack-update-headline',bunch)
    # Create an @view node as a clone of the @views node.
    bunch = u.beforeInsertNode(c.rootPosition())
    new_clone = vc.create_view_node(root)
    if new_clone:
        changed = True
        u.afterInsertNode(new_clone,'create-view-node',bunch)
    # Create a list of clones that have a representative node
    # outside of the root's tree.
    reps = [vc.find_representative_node(root,p)
        for p in root.children()
            if vc.is_cloned_outside_parent_tree(p)]
    reps = [z for z in reps if z is not None]
    if reps:
        changed = True
        bunch = u.beforeChangeTree(root)
        c.setChanged(True)
        # Prepend a unl: line for each cloned child.
        unls = ['unl: %s\n' % (vc.unl(p)) for p in reps]
        root.b = ''.join(unls) + root.b
        # Delete all child clones in the reps list.
        v_reps = set([p.v for p in reps])
        while True:
            for child in root.children():
                if child.v in v_reps:
                    child.doDelete()
                    break
            else: break
        u.afterChangeTree(root,'view-pack-tree',bunch)
    if changed:
        u.afterChangeGroup(root,'view-pack')
        c.selectPosition(root)
        c.redraw()
#@+node:ekr.20150312225028.44: *7* vc.create_view_node
def create_view_node(self,root):
    '''
    Create a clone of root as a child of the @views node.
    Return the *newly* cloned node, or None if it already exists.
    '''
    vc = self
    c = vc.c
    # Create a cloned child of the @views node if it doesn't exist.
    views = vc.find_at_views_node()
    for p in views.children():
        if p.v == c.p.v:
            return None
    p = root.clone()
    p.moveToLastChildOf(views)
    return p
#@+node:ekr.20150312225028.45: *6* vc.unpack
def unpack(self):
    '''
    Undoably unpack nodes corresponding to leading unl lines in c.p to child clones.
    Return True if the outline has, in fact, been changed.
    '''
    vc = self
    c,root,u = vc.c,vc.c.p,vc.c.undoer
    vc.init()
    # Find the leading unl: lines.
    i,lines,tag = 0,g.splitLines(root.b),'unl:'
    for s in lines:
        if s.startswith(tag): i += 1
        else: break
    changed = i > 0
    if changed:
        bunch = u.beforeChangeTree(root)
        # Restore the body
        root.b = ''.join(lines[i:])
        # Create clones for each unique unl.
        unls = list(set([s[len(tag):].strip() for s in lines[:i]]))
        for unl in unls:
            p = vc.find_absolute_unl_node(unl)
            if p: p.clone().moveToLastChildOf(root)
            else: g.trace('not found: %s' % (unl))
        c.setChanged(True)
        c.undoer.afterChangeTree(root,'view-unpack',bunch)
        c.redraw()
    return changed
#@+node:ekr.20150312225028.46: *6* vc.update_before_write_at_auto_file
def update_before_write_at_auto_file(self,root):
    '''
    Update the @auto-view node for root, an @auto node. Create @organizer,
    @existing-organizer, @clones and @headlines nodes as needed.
    This *must not* be called for trial writes.
    '''
    trace = False and not g.unitTesting
    vc = self
    c = vc.c
    changed = False
    t1 = time.clock()
    # Create lists of cloned and organizer nodes.
    clones,existing_organizers,organizers = \
        vc.find_special_nodes(root)
    # Delete all children of the @auto-view node for this @auto node.
    at_auto_view = vc.find_at_auto_view_node(root)
    if at_auto_view.hasChildren():
        changed = True
        at_auto_view.deleteAllChildren()
    # Create the single @clones node.
    if clones:
        at_clones = vc.find_at_clones_node(root)
        at_clones.b = ''.join(
            ['gnx: %s\nunl: %s\n' % (z[0],z[1]) for z in clones])
    # Create the single @organizers node.
    if organizers or existing_organizers:
        at_organizers = vc.find_at_organizers_node(root)
    # Create one @organizers: node for each organizer node.
    for p in organizers:
        # g.trace('organizer',p.h)
        at_organizer = at_organizers.insertAsLastChild()
        at_organizer.h = '@organizer: %s' % p.h
        # The organizer node's unl is implicit in each child's unl.
        at_organizer.b = '\n'.join([
            'unl: '+vc.relative_unl(z,root) for z in p.children()])
    # Create one @existing-organizer node for each existing organizer.
    ivar = vc.headline_ivar
    for p in existing_organizers:
        at_organizer = at_organizers.insertAsLastChild()
        h = getattr(p.v,ivar,p.h)
        if trace and h != p.h: g.trace('==>',h) # p.h,'==>',h
        at_organizer.h = '@existing-organizer: %s' % h
        # The organizer node's unl is implicit in each child's unl.
        at_organizer.b = '\n'.join([
            'unl: '+vc.relative_unl(z,root) for z in p.children()])
    # Create the single @headlines node.
    vc.create_at_headlines(root)
    if changed and not g.unitTesting:
        g.es_print('updated @views node in %4.2f sec.' % (
            time.clock()-t1))
    if changed:
        c.redraw()
    return at_auto_view # For at-file-to-at-auto command.
#@+node:ekr.20150312225028.47: *7* vc.create_at_headlines
def create_at_headlines(self,root):
    '''Create the @headlines node for root, an @auto file.'''
    vc = self
    c = vc.c
    result = []
    ivar = vc.headline_ivar
    for p in root.subtree():
        h = getattr(p.v,ivar,None)
        if h is not None and p.h != h:
            # g.trace('custom:',p.h,'imported:',h)
            unl = vc.relative_unl(p,root)
            aList = unl.split('-->')
            aList[-1] = h
            unl = '-->'.join(aList)
            result.append('imported unl: %s\nhead: %s\n' % (
                unl,p.h))
            delattr(p.v,ivar)
    if result:
        p = vc.find_at_headlines_node(root)
        p.b = ''.join(result)
#@+node:ekr.20150312225028.48: *7* vc.find_special_nodes
def find_special_nodes(self,root):
    '''
    Scan root's tree, looking for organizer and cloned nodes.
    Exclude organizers on imported organizers list.
    '''
    trace = False and not g.unitTesting
    verbose = False
    vc = self
    clones,existing_organizers,organizers = [],[],[]
    if trace: g.trace('imported existing',
        [v.h for v in vc.imported_organizers_list])
    for p in root.subtree():
        if p.isCloned():
            rep = vc.find_representative_node(root,p)
            if rep:
                unl = vc.relative_unl(p,root)
                gnx = rep.v.gnx
                clones.append((gnx,unl),)
        if p.v in vc.imported_organizers_list:
            # The node had children created by the importer.
            if trace and verbose: g.trace('ignore imported existing',p.h)
        elif vc.is_organizer_node(p,root):
            # p.hasChildren and p.b is empty, except for comments.
            if trace and verbose: g.trace('organizer',p.h)
            organizers.append(p.copy())
        elif p.hasChildren():
            if trace and verbose: g.trace('existing',p.h)
            existing_organizers.append(p.copy())
    return clones,existing_organizers,organizers
#@+node:ekr.20150312225028.49: *6* vc.update_after_read_at_auto_file & helpers
def update_after_read_at_auto_file(self,root):
    '''
    Recreate all organizer nodes and clones for a single @auto node
    using the corresponding @organizer: and @clones nodes.
    '''
    trace = True and not g.unitTesting
    vc = self
    c = vc.c
    if not vc.is_at_auto_node(root):
        return # Not an error: it might be and @auto-rst node.
    old_changed = c.isChanged()
    try:
        vc.init()
        vc.root = root.copy()
        t1 = time.clock()
        vc.trial_write_1 = vc.trial_write(root)
        t2 = time.clock()
        at_organizers = vc.has_at_organizers_node(root)
        t3 = time.clock()
        if at_organizers:
            vc.create_organizer_nodes(at_organizers,root)
        t4 = time.clock()
        at_clones = vc.has_at_clones_node(root)
        if at_clones:
            vc.create_clone_links(at_clones,root)
        t5 = time.clock()
        n = len(vc.work_list)
        ok = vc.check(root)
        t6 = time.clock()
        if ok:
            vc.update_headlines_after_read(root)
        t7 = time.clock()
        c.setChanged(old_changed if ok else False)
            # To do: revert if not ok.
    except Exception:
        g.es_exception()
        n = 0
        ok = False
    if trace:
        if t7-t1 > 0.5: g.trace(
            '\n  trial_write:                 %4.2f sec' % (t2-t1),
            # '\n  has_at_organizers_node:    %4.2f sec' % (t3-t2),
            '\n  create_organizer_nodes:      %4.2f sec' % (t4-t3),
            '\n  create_clone_links:          %4.2f sec' % (t5-t4),
            '\n  check:                       %4.2f sec' % (t6-t5),
            '\n  update_headlines_after_read: %4.2f sec' % (t7-t6),
            '\n  total:                       %4.2f sec' % (t7-t1))
            # '\n  file:',root.h)
        # else: g.trace('total: %4.2f sec' % (t7-t1),root.h)
    if ok and n > 0:
        g.es('rearragned: %s' % (root.h),color='blue')
        g.es('moved %s nodes in %4.2f sec.' % (n,t7-t1))
        g.trace('@auto-view moved %s nodes in %4.2f sec. for' % (
            n,t2),root.h,noname=True)
    c.selectPosition(root)
    c.redraw()
    return ok
#@+node:ekr.20150312225028.50: *7* vc.check
def check (self,root):
    '''
    Compare a trial write or root with the vc.trail_write_1.
    Unlike the perfect-import checks done by the importer,
    we expecct an *exact* match, regardless of language.
    '''
    trace = True # and not g.unitTesting
    vc = self
    trial1 = vc.trial_write_1
    trial2 = vc.trial_write(root)
    if trial1 != trial2:
        g.pr('') # Don't use print: it does not appear with the traces.
        g.es_print('perfect import check failed for:',color='red')
        g.es_print(root.h,color='red')
        if trace:
            vc.compare_trial_writes(trial1,trial2)
            g.pr('')
    return trial1 == trial2
#@+node:ekr.20150312225028.51: *7* vc.create_clone_link
def create_clone_link(self,gnx,root,unl):
    '''
    Replace the node in the @auto tree with the given unl by a
    clone of the node outside the @auto tree with the given gnx.
    '''
    trace = False and not g.unitTesting
    vc = self
    p1 = vc.find_position_for_relative_unl(root,unl)
    p2 = vc.find_gnx_node(gnx)
    if p1 and p2:
        if trace: g.trace('relink',gnx,p2.h,'->',p1.h)
        if p1.b == p2.b:
            p2._relinkAsCloneOf(p1)
            return True
        else:
            g.es('body text mismatch in relinked node',p1.h)
            return False
    else:
        if trace: g.trace('relink failed',gnx,root.h,unl)
        return False
#@+node:ekr.20150312225028.52: *7* vc.create_clone_links
def create_clone_links(self,at_clones,root):
    '''
    Recreate clone links from an @clones node.
    @clones nodes contain pairs of lines (gnx,unl)
    '''
    vc = self
    lines = g.splitLines(at_clones.b)
    gnxs = [s[4:].strip() for s in lines if s.startswith('gnx:')]
    unls = [s[4:].strip() for s in lines if s.startswith('unl:')]
    # g.trace('at_clones.b',at_clones.b)
    if len(gnxs) == len(unls):
        vc.headlines_dict = {} # May be out of date.
        ok = True
        for gnx,unl in zip(gnxs,unls):
            ok = ok and vc.create_clone_link(gnx,root,unl)
        return ok
    else:
        g.trace('bad @clones contents',gnxs,unls)
        return False
#@+node:ekr.20150312225028.53: *7* vc.create_organizer_nodes & helpers
def create_organizer_nodes(self,at_organizers,root):
    '''
    root is an @auto node. Create an organizer node in root's tree for each
    child @organizer: node of the given @organizers node.
    '''
    vc = self
    c = vc.c
    trace = False and not g.unitTesting
    t1 = time.clock()
    vc.pre_move_comments(root)
        # Merge comment nodes with the next node.
    t2 = time.clock()
    vc.precompute_all_data(at_organizers,root)
        # Init all data required for reading.
    t3 = time.clock()
    vc.demote(root)
        # Traverse root's tree, adding nodes to vc.work_list.
    t4 = time.clock()
    vc.move_nodes()
        # Move nodes on vc.work_list to their final locations.
    t5 = time.clock()
    vc.post_move_comments(root)
        # Move merged comments to parent organizer nodes.
    t6 = time.clock()
    if trace: g.trace(
        '\n  pre_move_comments:   %4.2f sec' % (t2-t1),
        '\n  precompute_all_data: %4.2f sec' % (t3-t2),
        '\n  demote:              %4.2f sec' % (t4-t3),
        '\n  move_nodes:          %4.2f sec' % (t5-t4),
        '\n  post_move_comments:  %4.2f sec' % (t6-t5))
#@+node:ekr.20150312225028.54: *7* vc.update_headlines_after_read
def update_headlines_after_read(self,root):
    '''Handle custom headlines for all imported nodes.'''
    trace = False and not g.unitTesting
    vc = self
    # Remember the original imported headlines.
    ivar = vc.headline_ivar
    for p in root.subtree():
        if not hasattr(p.v,ivar):
            setattr(p.v,ivar,p.h)
    # Update headlines from @headlines nodes.
    at_headlines = vc.has_at_headlines_node(root)
    tag1,tag2 = 'imported unl: ','head: '
    n1,n2 = len(tag1),len(tag2)
    if at_headlines:
        lines = g.splitLines(at_headlines.b)
        unls  = [s[n1:].strip() for s in lines if s.startswith(tag1)]
        heads = [s[n2:].strip() for s in lines if s.startswith(tag2)]
    else:
        unls,heads = [],[]
    if len(unls) == len(heads):
        vc.headlines_dict = {} # May be out of date.
        for unl,head in zip(unls,heads):
            p = vc.find_position_for_relative_unl(root,unl)
            if p:
                if trace: g.trace('unl:',unl,p.h,'==>',head)
                p.h = head
    else:
        g.trace('bad @headlines body',at_headlines.b)
#@+node:ekr.20150312225028.55: *5* vc.Main Lines
#@+node:ekr.20150312225028.56: *6* vc.precompute_all_data & helpers
def precompute_all_data(self,at_organizers,root):
    '''Precompute all data needed to reorganize nodes.'''
    trace = False and not g.unitTesting
    vc = self
    t1 = time.clock() 
    vc.find_imported_organizer_nodes(root)
        # Put all nodes with children on vc.imported_organizer_node_list
    t2 = time.clock()
    vc.create_organizer_data(at_organizers,root)
        # Create OrganizerData objects for all @organizer:
        # and @existing-organizer: nodes.
    t3 = time.clock()
    vc.create_actual_organizer_nodes()
        # Create the organizer nodes in holding cells so positions remain valid.
    t4 = time.clock()
    vc.create_tree_structure(root)
        # Set od.parent_od, od.children & od.descendants for all ods.
    t5 = time.clock()
    vc.compute_all_organized_positions(root)
        # Compute the positions organized by each organizer.
        # ** Most of the time is spent here **.
    t6 = time.clock()
    vc.create_anchors_d()
        # Create the dictionary that associates positions with ods.
    t7 = time.clock()
    if trace: g.trace(
        '\n  find_imported_organizer_nodes:   %4.2f sec' % (t2-t1),
        '\n  create_organizer_data:           %4.2f sec' % (t3-t2),
        '\n  create_actual_organizer_nodes:   %4.2f sec' % (t4-t3),
        '\n  create_tree_structure:           %4.2f sec' % (t5-t4),
        '\n  compute_all_organized_positions: %4.2f sec' % (t6-t5),
        '\n  create_anchors_d:                %4.2f sec' % (t7-t6))
#@+node:ekr.20150312225028.57: *7* 1: vc.find_imported_organizer_nodes
def find_imported_organizer_nodes(self,root):
    '''
    Put the VNode of all imported nodes with children on
    vc.imported_organizers_list.
    '''
    trace = False # and not g.unitTesting
    vc = self
    aList = []
    for p in root.subtree():
        if p.hasChildren():
            aList.append(p.v)
    vc.imported_organizers_list = list(set(aList))
    if trace: g.trace([z.h for z in vc.imported_organizers_list])
#@+node:ekr.20150312225028.58: *7* 2: vc.create_organizer_data (od.p & od.parent)
def create_organizer_data(self,at_organizers,root):
    '''
    Create OrganizerData nodes for all @organizer: and @existing-organizer:
    nodes in the given @organizers node.
    '''
    vc = self
    vc.create_ods(at_organizers)
    vc.finish_create_organizers(root)
    vc.finish_create_existing_organizers(root)
    for od in vc.all_ods:
        assert od.parent,(od.exists,od.h)
#@+node:ekr.20150312225028.59: *8* vc.create_ods
def create_ods(self,at_organizers):
    '''Create all organizer nodes and the associated lists.'''
    # Important: we must completely reinit all data here.
    vc = self
    tag1 = '@organizer:'
    tag2 = '@existing-organizer:'
    vc.all_ods,vc.existing_ods,vc.organizer_ods = [],[],[]
    for at_organizer in at_organizers.children():
        h = at_organizer.h
        for tag in (tag1,tag2):
            if h.startswith(tag):
                unls = vc.get_at_organizer_unls(at_organizer)
                if unls:
                    organizer_unl = vc.drop_unl_tail(unls[0])
                    h = h[len(tag):].strip()
                    od = OrganizerData(h,organizer_unl,unls)
                    vc.all_ods.append(od)
                    if tag == tag1:
                        vc.organizer_ods.append(od)
                        vc.organizer_unls.append(organizer_unl)
                    else:
                        vc.existing_ods.append(od)
                        # Do *not* append organizer_unl to the unl list.
                else:
                    g.trace('===== no unls:',at_organizer.h)
#@+node:ekr.20150312225028.60: *8* vc.finish_create_organizers
def finish_create_organizers(self,root):
    '''Finish creating all organizers.'''
    trace = False # and not g.unitTesting
    vc = self
    # Careful: we may delete items from this list.
    for od in vc.organizer_ods[:]: 
        od.source_unl = vc.source_unl(vc.organizer_unls,od.unl)
        od.parent = vc.find_position_for_relative_unl(root,od.source_unl)
        if od.parent:
            od.anchor = od.parent
            if trace: g.trace(od.h,
                # '\n  exists:',od.exists,
                # '\n  unl:',od.unl,
                # '\n  source (unl):',od.source_unl or repr(''),
                # '\n  anchor (pos):',od.anchor.h,
                # '\n  parent (pos):',od.parent.h,
            )
        else:
            # This is, most likely, a true error.
            g.trace('===== removing od:',od.h)
            vc.organizer_ods.remove(od)
            vc.all_ods.remove(od)
            assert od not in vc.existing_ods
            assert od not in vc.all_ods
#@+node:ekr.20150312225028.61: *8* vc.finish_create_existing_organizers
def finish_create_existing_organizers(self,root):
    '''Finish creating existing organizer nodes.'''
    trace = False # and not g.unitTesting
    vc = self
    # Careful: we may delete items from this list.
    for od in vc.existing_ods[:]:
        od.exists = True
        assert od.unl not in vc.organizer_unls
        od.source_unl = vc.source_unl(vc.organizer_unls,od.unl)
        od.p = vc.find_position_for_relative_unl(root,od.source_unl)
        if od.p:
            od.anchor = od.p
            assert od.p.h == od.h,(od.p.h,od.h)  
            od.parent = od.p # Here, od.parent represents the "source" p.
            if trace: g.trace(od.h,
                # '\n  exists:',od.exists,
                # '\n  unl:',od.unl,
                # '\n  source (unl):',od.source_unl or repr(''),
                # '\n  anchor (pos):',od.anchor.h,
                # '\n  parent (pos):',od.parent.h,
            )
        else:
            # This arises when the imported node name doesn't match.
            g.trace('===== removing existing organizer:',od.h)
            vc.existing_ods.remove(od)
            vc.all_ods.remove(od)
            assert od not in vc.existing_ods
            assert od not in vc.all_ods

#@+node:ekr.20150312225028.62: *7* 3: vc.create_actual_organizer_nodes
def create_actual_organizer_nodes(self):
    '''
    Create all organizer nodes as children of holding cells. These holding
    cells ensure that moving an organizer node leaves all other positions
    unchanged.
    '''
    vc = self
    c = vc.c
    last = c.lastTopLevel()
    temp = vc.temp_node = last.insertAfter()
    temp.h = 'ViewController.temp_node'
    for od in vc.organizer_ods:
        holding_cell = temp.insertAsLastChild()
        holding_cell.h = 'holding cell for ' + od.h
        od.p = holding_cell.insertAsLastChild()
        od.p.h = od.h
#@+node:ekr.20150312225028.63: *7* 4: vc.create_tree_structure & helper
def create_tree_structure(self,root):
    '''Set od.parent_od, od.children & od.descendants for all ods.'''
    trace = False and not g.unitTesting
    vc = self
    # if trace: g.trace([z.h for z in data_list],g.callers())
    organizer_unls = [z.unl for z in vc.all_ods]
    for od in vc.all_ods:
        for unl in od.unls:
            if unl in organizer_unls:
                i = organizer_unls.index(unl)
                d2 = vc.all_ods[i]
                # if trace: g.trace('found organizer unl:',od.h,'==>',d2.h)
                od.children.append(d2)
                d2.parent_od = od
    # create_organizer_data now ensures od.parent is set.
    for od in vc.all_ods:
        assert od.parent,od.h
    # Extend the descendant lists.
    for od in vc.all_ods:
        vc.compute_descendants(od)
        assert od.descendants is not None
    if trace:
        def tail(head,unl):
            return str(unl[len(head):]) if unl.startswith(head) else str(unl)
        for od in vc.all_ods:
            g.trace(
                '\n  od:',od.h,
                '\n  unl:',od.unl,
                '\n  unls:', [tail(od.unl,z) for z in od.unls],
                '\n  source (unl):',od.source_unl or repr(''),
                '\n  parent (pos):', od.parent.h,
                '\n  children:',[z.h for z in od.children],
                '\n  descendants:',[str(z.h) for z in od.descendants])
#@+node:ekr.20150312225028.64: *8* vc.compute_descendants
def compute_descendants(self,od,level=0,result=None):
    '''Compute the descendant od nodes of od.'''
    trace = False # and not g.unitTesting
    vc = self
    if level == 0:
        result = []
    if od.descendants is None:
        for child in od.children:
            result.append(child)
            result.extend(vc.compute_descendants(child,level+1,result))
            result = list(set(result))
        if level == 0:
            od.descendants = result
            if trace: g.trace(od.h,[z.h for z in result])
        return result
    else:
        if trace: g.trace('cached',od.h,[z.h for z in od.descendants])
        return od.descendants
#@+node:ekr.20150312225028.65: *7* 5: vc.compute_all_organized_positions
def compute_all_organized_positions(self,root):
    '''Compute the list of positions organized by every od.'''
    trace = False and not g.unitTesting
    vc = self
    for od in vc.all_ods:
        if od.unls:
            # Do a full search only for the first unl.
            # parent = vc.find_position_for_relative_unl(root,od.unls[0])
            if True: # parent:
                for unl in od.unls:
                    p = vc.find_position_for_relative_unl(root,unl)
                    # p = vc.find_position_for_relative_unl(parent,vc.unl_tail(unl))
                    if p:
                        od.organized_nodes.append(p.copy())
                    if trace: g.trace('exists:',od.exists,
                        'od:',od.h,'unl:',unl,
                        'p:',p and p.h or '===== None')
            else:
                g.trace('fail',od.unls[0])
#@+node:ekr.20150312225028.66: *7* 6: vc.create_anchors_d
def create_anchors_d (self):
    '''
    Create vc.anchors_d.
    Keys are positions, values are lists of ods having that anchor.
    '''
    trace = False # and not g.unitTesting
    vc = self
    d = {}
    if trace: g.trace('all_ods',[z.h for z in vc.all_ods])
    for od in vc.all_ods:
        # Compute the anchor if it does not yet exists.
        # Valid now that p.__hash__ exists.
        key = od.anchor
        # key = '.'.join([str(z) for z in od.anchor.sort_key(od.anchor)])
        # key = '%s (%s)' % (key,od.anchor.h)
        aList = d.get(key,[])
        # g.trace(od.h,od.anchor.h,key,aList)
        aList.append(od)
        d[key] = aList
    if trace:
        for key in sorted(d.keys()):
            g.trace('od.anchor: %s ods: [%s]' % (key.h,','.join(z.h for z in d.get(key))))
    vc.anchors_d = d
#@+node:ekr.20150312225028.67: *6* vc.demote & helpers
def demote(self,root):
    '''
    The main line of the @auto-view algorithm. Traverse root's entire tree,
    placing items on the global work list.
    '''
    trace = False # and not g.unitTesting
    trace_loop = True
    vc = self
    active = None # The active od.
    vc.pending = [] # Lists of pending demotions.
    d = vc.anchor_offset_d # For traces.
    for p in root.subtree():
        parent = p.parent()
        if trace and trace_loop:
            if 1:
                g.trace('-----',p.childIndex(),p.h)
            else:
                g.trace(
                    '=====\np:',p.h,
                    'childIndex',p.childIndex(),
                    '\nparent:',parent.h,
                    'parent:offset',d.get(parent,0))
        vc.n_nodes_scanned += 1
        vc.terminate_organizers(active,parent)
        found = vc.find_organizer(parent,p)
        if found:
            pass # vc.enter_organizers(found,p)
        else:
            pass # vc.terminate_all_open_organizers()
        if trace and trace_loop:
            g.trace(
                'active:',active and active.h or 'None',
                'found:',found and found.h or 'None')
        # The main case statement...
        if found is None and active:
            vc.add_to_pending(active,p)
        elif found is None and not active:
            # Pending nodes will *not* be organized.
            vc.clear_pending(None,p)
        elif found and found == active:
            # Pending nodes *will* be organized.
            for z in vc.pending:
                active2,child2 = z
                vc.add(active2,child2,'found==active:pending')
            vc.pending = []
            vc.add(active,p,'found==active')
        elif found and found != active:
            # Pending nodes will *not* be organized.
            vc.clear_pending(found,p)
            active = found
            vc.enter_organizers(found,p)
            vc.add(active,p,'found!=active')
        else: assert False,'can not happen'
#@+node:ekr.20150312225028.68: *7* vc.add
def add(self,active,p,tag):
    '''
    Add p, an existing (imported) node to the global work list.
    Subtract 1 from the vc.anchor_offset_d entry for p.parent().
    
    Exception: do *nothing* if p is a child of an existing organizer node.
    '''
    trace = False # and not g.unitTesting
    verbose = False
    vc = self
    # g.trace(active,g.callers())
    if active.p == p.parent() and active.exists:
        if trace and verbose: g.trace('===== do nothing',active.h,p.h)
    else:
        data = active.p,p.copy()
        vc.add_to_work_list(data,tag)
        vc.anchor_decr(anchor=p.parent(),p=p)
        
#@+node:ekr.20150312225028.69: *7* vc.add_organizer_node
def add_organizer_node (self,od,p):
    '''
    Add od to the appropriate move list.
    p is the existing node that caused od to be added.
    '''
    trace = True # and not g.unitTesting
    verbose = False
    vc = self
    # g.trace(od.h,'parent',od.parent_od and od.parent_od.h or 'None')
    if od.parent_od:
        # Not a bare organizer: a child of another organizer node.
        # If this is an existing organizer, it's *position* may have
        # been moved without active.moved being set.
        data = od.parent_od.p,od.p
        if data in vc.work_list:
            if trace and verbose: g.trace(
                '**** duplicate 1: setting moved bit.',od.h)
            od.moved = True
        elif od.parent_od.exists:    
            anchor = od.parent_od.p
            n = vc.anchor_incr(anchor,p) + p.childIndex()
            data = anchor,od.p,n
            # g.trace('anchor:',anchor.h,'p:',p.h,'childIndex',p.childIndex())
            vc.add_to_bare_list(data,'non-bare existing')
        else:
            vc.add_to_work_list(data,'non-bare')
    elif od.p == od.anchor:
        if trace and verbose: g.trace(
            '***** existing organizer: do not move:',od.h)
    else:
        # This can be pre-computed?
        bare_list = [p for parent,p,n in vc.global_bare_organizer_node_list]
        if od.p in bare_list:
            if trace and verbose: g.trace(
                '**** duplicate 2: setting moved bit.',od.h)
            od.moved = True
        else:
            # A bare organizer node: a child of an *ordinary* node.
            anchor = p.parent()
            n = vc.anchor_incr(anchor,p) + p.childIndex()
            data = anchor,od.p,n
            vc.add_to_bare_list(data,'bare')
#@+node:ekr.20150312225028.70: *7* vc.add_to_bare_list
def add_to_bare_list(self,data,tag):
    '''Add data to the bare organizer list, with tracing.'''
    trace = False # and not g.unitTesting
    vc = self
    # Prevent duplicagtes.
    anchor,p,n = data
    for data2 in vc.global_bare_organizer_node_list:
        a2,p2,n2 = data2
        if p == p2:
            if trace: g.trace('ignore duplicate',
                'n:',n,anchor.h,'==>',p.h)
            return
    vc.global_bare_organizer_node_list.append(data)
    if trace:
        anchor,p,n = data
        g.trace('=====',tag,'n:',n,anchor.h,'==>',p.h)
            # '\n  anchor:',anchor.h,
            # '\n  p:',p.h)
#@+node:ekr.20150312225028.71: *7* vc.add_to_pending
def add_to_pending(self,active,p):
    trace = False # and not g.unitTesting
    vc = self
    if trace: g.trace(active.p.h,'==>',p.h)
    vc.pending.append((active,p.copy()),)
#@+node:ekr.20150312225028.72: *7* vc.add_to_work_list
def add_to_work_list(self,data,tag):
    '''Append the data to the work list, with tracing.'''
    trace = False # and not g.unitTesting
    vc = self
    vc.work_list.append(data)
    if trace:
        active,p = data
        g.trace('=====',tag,active.h,'==>',p.h)
#@+node:ekr.20150312225028.73: *7* vc.anchor_decr
def anchor_decr(self,anchor,p): # p is only for traces.
    '''
    Decrement the anchor dict for the given anchor node.
    Return the *previous* value.
    '''
    trace = False # and not g.unitTesting
    vc = self
    d = vc.anchor_offset_d
    n = d.get(anchor,0)
    d[anchor] = n - 1
    if trace: g.trace(n-1,anchor.h,'==>',p.h)
    return n
#@+node:ekr.20150312225028.74: *7* vc.anchor_incr
def anchor_incr(self,anchor,p): # p is only for traces.
    '''
    Increment the anchor dict for the given anchor node.
    Return the *previous* value.
    '''
    trace = False # and not g.unitTesting
    vc = self
    d = vc.anchor_offset_d
    n = d.get(anchor,0)
    d[anchor] = n + 1
    if trace: g.trace(n+1,anchor.h,'==>',p.h)
    return n
#@+node:ekr.20150312225028.75: *7* vc.clear_pending
def clear_pending(self,active,p):
    '''Clear the appropriate entries from the pending list.'''
    trace = False # and not g.unitTesting
    vc = self
    if trace: g.trace('===== clear pending',len(vc.pending))
    if False: # active and active.parent_od:
        for data in vc.pending:
            data = active.parent_od.p,data[1]
            vc.add_to_work_list(data,'clear-pending-to-active')
    vc.pending = []
#@+node:ekr.20150312225028.76: *7* vc.enter_organizers
def enter_organizers(self,od,p):
    '''Enter all organizers whose anchors are p.'''
    vc = self
    ods = []
    while od:
        ods.append(od)
        od = od.parent_od
    if ods:
        for od in reversed(ods):
            vc.add_organizer_node(od,p)
#@+node:ekr.20150312225028.77: *7* vc.find_organizer
def find_organizer(self,parent,p):
    '''Return the organizer that organizers p, if any.'''
    trace = False # and not g.unitTesting
    vc = self
    anchor = parent
    ods = vc.anchors_d.get(anchor,[])
    for od in ods:
        if p in od.organized_nodes:
            if trace: g.trace('found:',od.h,'for',p.h)
            return od
    return None
#@+node:ekr.20150312225028.78: *7* vc.terminate_organizers
def terminate_organizers(self,active,p):
    '''Terminate all organizers whose anchors are not ancestors of p.'''
    trace = False # and not g.unitTesting
    od = active
    while od and od.anchor != p and od.anchor.isAncestorOf(p):
        if not od.closed:
            if trace: g.trace('===== closing',od.h)
            od.closed = True
        od = od.parent_od
#@+node:ekr.20150312225028.79: *7* vc.terminate_all_open_organizers
def terminate_all_open_organizers(self):
    '''Terminate all open organizers.'''
    trace = True # and not g.unitTesting
    if 0:
        g.trace()
        for od in self.all_ods:
            if od.opened and not od.closed:
                if trace: g.trace('===== closing',od.h)
                od.closed = True
#@+node:ekr.20150312225028.80: *6* vc.move_nodes & helpers
def move_nodes(self):
    '''Move nodes to their final location and delete the temp node.'''
    trace = False # and not g.unitTesting
    vc = self
    vc.move_nodes_to_organizers(trace)
    vc.move_bare_organizers(trace)
    vc.temp_node.doDelete()
#@+node:ekr.20150312225028.81: *7* vc.move_nodes_to_organizers
def move_nodes_to_organizers(self,trace):
    '''Move all nodes in the work_list.'''
    trace = False # and not g.unitTesting
    trace_dict = False
    trace_moves = False
    trace_deletes = False
    vc = self
    if trace: # A highly useful trace!
        g.trace('\n\nunsorted_list...\n%s' % (
            '\n'.join(['%40s ==> %s' % (parent.h,p.h)
                for parent,p in vc.work_list])))
    # Create a dictionary of each organizers children.
    d = {}
    for parent,p in vc.work_list:
        # This key must remain stable if parent moves.
        key = parent
        aList = d.get(key,[])
        aList.append(p)
        # g.trace(key,[z.h for z in aList])
        d[key] = aList
    if trace and trace_dict:
        # g.trace('d...',sorted([z.h for z in d.keys()]))
        g.trace('d{}...')
        for key in sorted(d.keys()):
            aList = [z.h for z in d.get(key)]
            g.trace('%s %-20s %s' % (id(key),key.h,vc.dump_list(aList,indent=29)))
    # Move *copies* of non-organizer nodes to each organizer.
    organizers = list(d.keys())
    existing_organizers = [z.p.copy() for z in vc.existing_ods]
    moved_existing_organizers = {} # Keys are vnodes, values are positions.
    for parent in organizers:
        aList = d.get(parent,[])
        if trace and trace_moves:
            g.trace('===== moving/copying:',parent.h,
                'with %s children:' % (len(aList)),
                '\n  '+'\n  '.join([z.h for z in aList]))
        for p in aList:
            if p in existing_organizers:
                if trace and trace_moves:
                    g.trace('copying existing organizer:',p.h)
                    g.trace('children:',
                    '\n  '+'\n  '.join([z.h for z in p.children()]))
                copy = vc.copy_tree_to_last_child_of(p,parent)
                old = moved_existing_organizers.get(p.v)
                if old and trace_moves:
                    g.trace('*********** overwrite',p.h)
                moved_existing_organizers[p.v] = copy
            elif p in organizers:
                if trace and trace_moves:
                    g.trace('moving organizer:',p.h)
                aList = d.get(p)
                if aList:
                    if trace and trace_moves: g.trace('**** relocating',
                        p.h,'children:',
                        '\n  '+'\n  '.join([z.h for z in p.children()]))
                    del d[p]
                p.moveToLastChildOf(parent)
                if aList:
                    d[p] = aList
            else:
                parent2 = moved_existing_organizers.get(parent.v)
                if parent2:
                    if trace and trace_moves:
                        g.trace('***** copying to relocated parent:',p.h)
                    vc.copy_tree_to_last_child_of(p,parent2)
                else:
                    if trace and trace_moves: g.trace('copying:',p.h)
                    vc.copy_tree_to_last_child_of(p,parent)
    # Finally, delete all the non-organizer nodes, in reverse outline order.
    def sort_key(od):
        parent,p = od
        return p.sort_key(p)
    sorted_list = sorted(vc.work_list,key=sort_key)
    if trace and trace_deletes:
        g.trace('===== deleting nodes in reverse outline order...')
    for parent,p in reversed(sorted_list):
        if p.v in moved_existing_organizers:
            if trace and trace_deletes:
                g.trace('deleting moved existing organizer:',p.h)
            p.doDelete()
        elif p not in organizers:
            if trace and trace_deletes:
                g.trace('deleting non-organizer:',p.h)
            p.doDelete()
#@+node:ekr.20150312225028.82: *7* vc.move_bare_organizers
def move_bare_organizers(self,trace):
    '''Move all nodes in global_bare_organizer_node_list.'''
    trace = False # and not g.unitTesting
    trace_data = True
    trace_move = True
    vc = self
    # For each parent, sort nodes on n.
    d = {} # Keys are vnodes, values are lists of tuples (n,parent,p)
    existing_organizers = [od.p for od in vc.existing_ods]
    if trace: g.trace('ignoring existing organizers:',
        [p.h for p in existing_organizers])
    for parent,p,n in vc.global_bare_organizer_node_list:
        if p not in existing_organizers:
            key = parent.v
            aList = d.get(key,[])
            if (parent,p,n) not in aList:
                aList.append((parent,p,n),)
                d[key] = aList
    # For each parent, add nodes in childIndex order.
    def key_func(obj):
        return obj[0]
    for key in d.keys():
        aList = d.get(key)
        for data in sorted(aList,key=key_func):
            parent,p,n = data
            n2 = parent.numberOfChildren()
            if trace and trace_data:
                g.trace(n,parent.h,'==>',p.h)
            if trace and trace_move: g.trace(
                'move: %-20s:' % (p.h),
                'to child: %2s' % (n),
                'of: %-20s' % (parent.h),
                'with:',n2,'children')
            p.moveToNthChildOf(parent,n)
#@+node:ekr.20150312225028.83: *7* vc.copy_tree_to_last_child_of
def copy_tree_to_last_child_of(self,p,parent):
    '''Copy p's tree to the last child of parent.'''
    vc = self
    assert p != parent,p
        # A failed assert leads to unbounded recursion.
    # print('copy_tree_to_last_child_of',p.h,parent.h)
    root = parent.insertAsLastChild()
    root.b,root.h = p.b,p.h
    root.v.u = copy.deepcopy(p.v.u)
    for child in p.children():
        vc.copy_tree_to_last_child_of(child,root)
    return root
#@+node:ekr.20150312225028.84: *5* vc.Helpers
#@+node:ekr.20150312225028.85: *6* vc.at_auto_view_body and match_at_auto_body
def at_auto_view_body(self,p):
    '''Return the body text for the @auto-view node for p.'''
    # Note: the unl of p relative to p is simply p.h,
    # so it is pointless to add that to the @auto-view node.
    return 'gnx: %s\n' % p.v.gnx

def match_at_auto_body(self,p,auto_view):
    '''Return True if any line of auto_view.b matches the expected gnx line.'''
    if 0: g.trace(p.b == 'gnx: %s\n' % auto_view.v.gnx,
        g.shortFileName(p.h),auto_view.v.gnx,p.b.strip())
    return p.b == 'gnx: %s\n' % auto_view.v.gnx
#@+node:ekr.20150312225028.86: *6* vc.clean_nodes (not used)
def clean_nodes(self):
    '''Delete @auto-view nodes with no corresponding @auto nodes.'''
    vc = self
    c = vc.c
    views = vc.has_at_views_node()
    if not views:
        return
    # Remember the gnx of all @auto nodes.
    d = {}
    for p in c.all_unique_positions():
        if vc.is_at_auto_node(p):
            d[p.v.gnx] = True
    # Remember all unused @auto-view nodes.
    delete = []
    for child in views.children():
        s = child.b and g.splitlines(child.b)
        gnx = s[len('gnx'):].strip()
        if gnx not in d:
            g.trace(child.h,gnx)
            delete.append(child.copy())
    for p in reversed(delete):
        p.doDelete()
    c.selectPosition(views)
#@+node:ekr.20150312225028.87: *6* vc.comments...
#@+node:ekr.20150312225028.88: *7* vc.comment_delims
def comment_delims(self,p):
    '''Return the comment delimiter in effect at p, an @auto node.'''
    vc = self
    c = vc.c
    d = g.get_directives_dict(p)
    s = d.get('language') or c.target_language
    language,single,start,end = g.set_language(s,0)
    return single,start,end
#@+node:ekr.20150312225028.89: *7* vc.delete_leading_comments
def delete_leading_comments(self,delims,p):
    '''
    Scan for leading comments from p and return them.
    At present, this only works for single-line comments.
    '''
    single,start,end = delims
    if single:
        lines = g.splitLines(p.b)
        result = []
        for s in lines:
            if s.strip().startswith(single):
                result.append(s)
            else: break
        if result:
            p.b = ''.join(lines[len(result):])
            # g.trace('len(result)',len(result),p.h)
            return ''.join(result)
    return None
#@+node:ekr.20150312225028.90: *7* vc.is_comment_node
def is_comment_node(self,p,root,delims=None):
    '''Return True if p.b contains nothing but comments or blank lines.'''
    vc = self
    if not delims:
        delims = vc.comment_delims(root)
    # pylint: disable=unpacking-non-sequence
    single,start,end = delims
    assert single or start and end,'bad delims: %r %r %r' % (single,start,end)
    if single:
        for s in g.splitLines(p.b):
            s = s.strip()
            if s and not s.startswith(single) and not g.isDirective(s):
                return False
        return True
    else:
        def check_comment(s):
            done,in_comment = False,True
            i = s.find(end)
            if i > -1:
                tail = s[i+len(end):].strip()
                if tail: done = True
                else: in_comment = False
            return done,in_comment
        
        done,in_comment = False,False
        for s in g.splitLines(p.b):
            s = s.strip()
            if not s:
                pass
            elif in_comment:
                done,in_comment = check_comment(s)
            elif g.isDirective(s):
                pass
            elif s.startswith(start):
                done,in_comment = check_comment(s[len(start):])
            else:
                # g.trace('fail 1: %r %r %r...\n%s' % (single,start,end,s)
                return False
            if done:
                return False
        # All lines pass.
        return True
#@+node:ekr.20150312225028.91: *7* vc.is_comment_organizer_node
# def is_comment_organizer_node(self,p,root):
    # '''
    # Return True if p is an organizer node in the given @auto tree.
    # '''
    # return p.hasChildren() and vc.is_comment_node(p,root)
#@+node:ekr.20150312225028.92: *7* vc.post_move_comments
def post_move_comments(self,root):
    '''Move comments from the start of nodes to their parent organizer node.'''
    vc = self
    c = vc.c
    delims = vc.comment_delims(root)
    for p in root.subtree():
        if p.hasChildren() and not p.b:
            s = vc.delete_leading_comments(delims,p.firstChild())
            if s:
                p.b = s
                # g.trace(p.h)
#@+node:ekr.20150312225028.93: *7* vc.pre_move_comments
def pre_move_comments(self,root):
    '''
    Move comments from comment nodes to the next node.
    This must be done before any other processing.
    '''
    vc = self
    c = vc.c
    delims = vc.comment_delims(root)
    aList = []
    for p in root.subtree():
        if p.hasNext() and vc.is_comment_node(p,root,delims=delims):
            aList.append(p.copy())
            next = p.next()
            if p.b: next.b = p.b + next.b
    # g.trace([z.h for z in aList])
    c.deletePositionsInList(aList)
        # This sets c.changed.
#@+node:ekr.20150312225028.94: *6* vc.find...
# The find commands create the node if not found.
#@+node:ekr.20150312225028.95: *7* vc.find_absolute_unl_node
def find_absolute_unl_node(self,unl,priority_header=False):
    '''Return a node matching the given absolute unl.
    If priority_header == True and the node is not found, it will return the longest matching UNL starting from the tail
    '''
    import re
    pos_pattern = re.compile(r':(\d+),?(\d+)?$')
    vc = self
    aList = unl.split('-->')
    if aList:
        first,rest = aList[0],'-->'.join(aList[1:])
        count = 0
        pos = re.findall(pos_pattern,first)
        nth_sib,pos = pos[0] if pos else (0,0)
        pos = int(pos) if pos else 0
        nth_sib = int(nth_sib)
        first = re.sub(pos_pattern,"",first).replace('--%3E','-->')
        for parent in vc.c.rootPosition().self_and_siblings():
            if parent.h.strip() == first.strip():
                if pos == count:
                    if rest:
                        return vc.find_position_for_relative_unl(parent,rest,priority_header=priority_header)
                    else:
                        return parent
                count = count+1
        #Here we could find and return the nth_sib if an exact header match was not found
    return None
#@+node:ekr.20150312225028.96: *7* vc.find_at_auto_view_node & helper
def find_at_auto_view_node (self,root):
    '''
    Return the @auto-view node for root, an @auto node.
    Create the node if it does not exist.
    '''
    vc = self
    views = vc.find_at_views_node()
    p = vc.has_at_auto_view_node(root)
    if not p:
        p = views.insertAsLastChild()
        p.h = '@auto-view:' + root.h[len('@auto'):].strip()
        p.b = vc.at_auto_view_body(root)
    return p
#@+node:ekr.20150312225028.97: *7* vc.find_clones_node
def find_at_clones_node(self,root):
    '''
    Find the @clones node for root, an @auto node.
    Create the @clones node if it does not exist.
    '''
    vc = self
    c = vc.c
    h = '@clones'
    auto_view = vc.find_at_auto_view_node(root)
    p = g.findNodeInTree(c,auto_view,h)
    if not p:
        p = auto_view.insertAsLastChild()
        p.h = h
    return p
#@+node:ekr.20150312225028.98: *7* vc.find_at_headlines_node
def find_at_headlines_node(self,root):
    '''
    Find the @headlines node for root, an @auto node.
    Create the @headlines node if it does not exist.
    '''
    vc = self
    c = vc.c
    h = '@headlines'
    auto_view = vc.find_at_auto_view_node(root)
    p = g.findNodeInTree(c,auto_view,h)
    if not p:
        p = auto_view.insertAsLastChild()
        p.h = h
    return p
#@+node:ekr.20150312225028.99: *7* vc.find_gnx_node
def find_gnx_node(self,gnx):
    '''Return the first position having the given gnx.'''
    # This is part of the read logic, so newly-imported
    # nodes will never have the given gnx.
    vc = self
    for p in vc.c.all_unique_positions():
        if p.v.gnx == gnx:
            return p
    return None
#@+node:ekr.20150312225028.100: *7* vc.find_organizers_node
def find_at_organizers_node(self,root):
    '''
    Find the @organizers node for root, and @auto node.
    Create the @organizers node if it doesn't exist.
    '''
    vc = self
    c = vc.c
    h = '@organizers'
    auto_view = vc.find_at_auto_view_node(root)
    p = g.findNodeInTree(c,auto_view,h)
    if not p:
        p = auto_view.insertAsLastChild()
        p.h = h
    return p
#@+node:ekr.20150312225028.101: *7* vc.find_position_for_relative_unl
def find_position_for_relative_unl(self,parent,unl,priority_header=False):
    '''
    Return the node in parent's subtree matching the given unl.
    The unl is relative to the parent position.
    If priority_header == True and the node is not found, it will return the longest matching UNL starting from the tail
    '''
    # This is called from finish_create_organizers & compute_all_organized_positions.
    trace = False # and not g.unitTesting
    trace_loop = True
    trace_success = False
    vc = self
    if not unl:
        if trace and trace_success:
            g.trace('return parent for empty unl:',parent.h)
        return parent
    # The new, simpler way: drop components of the unl automatically.
    drop,p = [],parent # for debugging.
    # if trace: g.trace('p:',p.h,'unl:',unl)
    import re
    pos_pattern = re.compile(r':(\d+),?(\d+)?$')
    for s in unl.split('-->'):
        found = False # The last part must match.
        if 1:
            # Create the list of children on the fly.
            aList = vc.headlines_dict.get(p.v)
            if aList is None:
                aList = [z.h for z in p.children()]
                vc.headlines_dict[p.v] = aList
            try:
                pos = re.findall(pos_pattern,s)
                nth_sib,pos = pos[0] if pos else (0,0)
                pos = int(pos) if pos else 0
                nth_sib = int(nth_sib)
                s = re.sub(pos_pattern,"",s).replace('--%3E','-->')
                indices = [i for i, x in enumerate(aList) if x == s]
                if len(indices)>pos:
                    #First we try the nth node with same header
                    n = indices[pos]
                    p = p.nthChild(n)
                    found = True
                elif len(indices)>0:
                    #Then we try any node with same header
                    n = indices[-1]
                    p = p.nthChild(n)
                    found = True
                elif not priority_header:
                    #Then we go for the child index if return_pos is true
                    if len(aList)>nth_sib:
                        n = nth_sib
                    else:
                        n = len(aList)-1
                    if n>-1:
                        p = p.nthChild(n)
                    else:
                        g.es('Partial UNL match: Referenced level is higher than '+str(p.level()))
                    found = True
                if trace and trace_loop: g.trace('match:',s)
            except ValueError: # s not in aList.
                if trace and trace_loop: g.trace('drop:',s)
                drop.append(s)
        else: # old code.
            for child in p.children():
                if child.h == s:
                    p = child
                    found = True
                    if trace and trace_loop: g.trace('match:',s)
                    break
                # elif trace and trace_loop: g.trace('no match:',child.h)
            else:
                if trace and trace_loop: g.trace('drop:',s)
                drop.append(s)
    if not found and priority_header:
        aList = []
        for p in vc.c.all_unique_positions():
            if p.h.replace('--%3E','-->') in unl:
                aList.append((p.copy(),p.get_UNL(False,False,True)))
        unl_list = [re.sub(pos_pattern,"",x).replace('--%3E','-->') for x in unl.split('-->')]
        for iter_unl in aList:
            maxcount = 0
            count = 0
            compare_list = unl_list[:]
            for header in reversed(iter_unl[1].split('-->')):
                if re.sub(pos_pattern,"",header).replace('--%3E','-->') == compare_list[-1]:
                    count = count+1
                    compare_list.pop(-1)
                else:
                    break
            if count > maxcount:
                p = iter_unl[0]
                found = True
    if found:
        if trace and trace_success:
            g.trace('found unl:',unl,'parent:',p.h,'drop',drop)
    else:
        if trace: g.trace('===== unl not found:',unl,'parent:',p.h,'drop',drop)
    return p if found else None
#@+node:ekr.20150312225028.102: *7* vc.find_representative_node
def find_representative_node (self,root,target):
    '''
    root is an @auto node. target is a clones node within root's tree.
    Return a node *outside* of root's tree that is cloned to target,
    preferring nodes outside any @<file> tree.
    Never return any node in any @views or @view tree.
    '''
    trace = False and not g.unitTesting
    assert target
    assert root
    vc = self
    # Pass 1: accept only nodes outside any @file tree.
    p = vc.c.rootPosition()
    while p:
        if p.h.startswith('@view'):
            p.moveToNodeAfterTree()
        elif p.isAnyAtFileNode():
            p.moveToNodeAfterTree()
        elif p.v == target.v:
            if trace: g.trace('success 1:',p,p.parent())
            return p
        else:
            p.moveToThreadNext()
    # Pass 2: accept any node outside the root tree.
    p = vc.c.rootPosition()
    while p:
        if p.h.startswith('@view'):
            p.moveToNodeAfterTree()
        elif p == root:
            p.moveToNodeAfterTree()
        elif p.v == target.v:
            if trace: g.trace('success 2:',p,p.parent())
            return p
        else:
            p.moveToThreadNext()
    g.trace('no representative node for:',target,'parent:',target.parent())
    return None
#@+node:ekr.20150312225028.103: *7* vc.find_views_node
def find_at_views_node(self):
    '''
    Find the first @views node in the outline.
    If it does not exist, create it as the *last* top-level node,
    so that no existing positions become invalid.
    '''
    vc = self
    c = vc.c
    p = g.findNodeAnywhere(c,'@views')
    if not p:
        last = c.rootPosition()
        while last.hasNext():
            last.moveToNext()
        p = last.insertAfter()
        p.h = '@views'
        # c.selectPosition(p)
        # c.redraw()
    return p
#@+node:ekr.20150312225028.104: *6* vc.has...
# The has commands return None if the node does not exist.
#@+node:ekr.20150312225028.105: *7* vc.has_at_auto_view_node
def has_at_auto_view_node(self,root):
    '''
    Return the @auto-view node corresponding to root, an @root node.
    Return None if no such node exists.
    '''
    vc = self
    c = vc.c
    assert vc.is_at_auto_node(root) or vc.is_at_file_node(root),root
    views = g.findNodeAnywhere(c,'@views')
    if views:
        # Find a direct child of views with matching headline and body.
        for p in views.children():
            if vc.match_at_auto_body(p,root):
                return p
    return None
#@+node:ekr.20150312225028.106: *7* vc.has_clones_node
def has_at_clones_node(self,root):
    '''
    Find the @clones node for an @auto node with the given unl.
    Return None if it does not exist.
    '''
    vc = self
    p = vc.has_at_auto_view_node(root)
    return p and g.findNodeInTree(vc.c,p,'@clones')
#@+node:ekr.20150312225028.107: *7* vc.has_at_headlines_node
def has_at_headlines_node(self,root):
    '''
    Find the @clones node for an @auto node with the given unl.
    Return None if it does not exist.
    '''
    vc = self
    p = vc.has_at_auto_view_node(root)
    return p and g.findNodeInTree(vc.c,p,'@headlines')
#@+node:ekr.20150312225028.108: *7* vc.has_organizers_node
def has_at_organizers_node(self,root):
    '''
    Find the @organizers node for root, an @auto node.
    Return None if it does not exist.
    '''
    vc = self
    p = vc.has_at_auto_view_node(root)
    return p and g.findNodeInTree(vc.c,p,'@organizers')
#@+node:ekr.20150312225028.109: *7* vc.has_views_node
def has_at_views_node(self):
    '''Return the @views or None if it does not exist.'''
    vc = self
    return g.findNodeAnywhere(vc.c,'@views')
#@+node:ekr.20150312225028.110: *6* vc.is...
#@+node:ekr.20150312225028.111: *7* vc.is_at_auto_node
def is_at_auto_node(self,p):
    '''Return True if p is an @auto node.'''
    return g.match_word(p.h,0,'@auto') and not g.match(p.h,0,'@auto-')
        # Does not match @auto-rst, etc.

def is_at_file_node(self,p):
    '''Return True if p is an @file node.'''
    return g.match_word(p.h,0,'@file')
#@+node:ekr.20150312225028.112: *7* vc.is_cloned_outside_parent_tree
def is_cloned_outside_parent_tree(self,p):
    '''Return True if a clone of p exists outside the tree of p.parent().'''
    return len(list(set(p.v.parents))) > 1
#@+node:ekr.20150312225028.113: *7* vc.is_organizer_node
def is_organizer_node(self,p,root):
    '''
    Return True if p is an organizer node in the given @auto tree.
    '''
    vc = self
    return p.hasChildren() and vc.is_comment_node(p,root)

#@+node:ekr.20150312225028.114: *6* vc.testing...
#@+node:ekr.20150312225028.115: *7* vc.compare_test_trees
def compare_test_trees(self,root1,root2):
    '''
    Compare the subtrees whose roots are given.
    This is called only from unit tests.
    '''
    vc = self
    s1,s2 = vc.trial_write(root1),vc.trial_write(root2)
    if s1 == s2:
        return True
    g.trace('Compare:',root1.h,root2.h)
    p2 = root2.copy().moveToThreadNext()
    for p1 in root1.subtree():
        if p1.h == p2.h:
            g.trace('Match:',p1.h)
        else:
            g.trace('Fail: %s != %s' % (p1.h,p2.h))
            break
        p2.moveToThreadNext()
    return False
#@+node:ekr.20150312225028.116: *7* vc.compare_trial_writes
def compare_trial_writes(self,s1,s2):
    '''
    Compare the two strings, the results of trial writes.
    Stop the comparison after the first mismatch.
    '''
    trace_matches = False
    full_compare = False
    lines1,lines2 = g.splitLines(s1),g.splitLines(s2)
    i,n1,n2 = 0,len(lines1),len(lines2)
    while i < n1 and i < n2:
        s1,s2 = lines1[i].rstrip(),lines2[i].rstrip()
        i += 1
        if s1 == s2:
            if trace_matches: g.trace('Match:',s1)
        else:
            g.trace('Fail:  %s != %s' % (s1,s2))
            if not full_compare: return
    if i < n1:
        g.trace('Extra line 1:',lines1[i])
    if i < n2:
        g.trace('Extra line 2:',lines2[i])
#@+node:ekr.20150312225028.117: *7* vc.dump_list
def dump_list(self,aList,indent=4):
    '''Dump a list, one item per line.'''
    lead = '\n' + ' '*indent
    return lead+lead.join(sorted(aList))
#@+node:ekr.20150312225028.118: *7* vc.trial_write
def trial_write(self,root):
    '''
    Return a trial write of outline whose root is given.
    
    **Important**: the @auto import and write code end all nodes with
    newlines. Because no imported nodes are empty, the code below is
    *exactly* equivalent to the @auto write code as far as trailing
    newlines are concerned. Furthermore, we can treat Leo directives as
    ordinary text here.
    '''
    vc = self
    if 1:
        # Do a full trial write, exactly as will be done later.
        at = vc.c.atFileCommands
        ok = at.writeOneAtAutoNode(root,
            toString=True,force=True,trialWrite=True)
        if ok:
            return at.stringOutput
        else:
            g.trace('===== can not happen')
            return ''
    elif 1:
        # Concatenate all body text.  Close, but not exact.
        return ''.join([p.b for p in root.self_and_subtree()])
    else:
        # Compare headlines, ignoring nodes without body text and comment nodes.
        # This was handy during early development.
        return '\n'.join([p.h for p in root.self_and_subtree()
            if p.b and not p.h.startswith('#')])
#@+node:ekr.20150312225028.119: *6* vc.unls...
#@+node:ekr.20150312225028.120: *7* vc.drop_all_organizers_in_unl
def drop_all_organizers_in_unl(self,organizer_unls,unl):
    '''Drop all organizer unl's in unl, recreating the imported unl.'''
    vc = self
    def unl_sort_key(s):
        return s.count('-->')
    for s in reversed(sorted(organizer_unls,key=unl_sort_key)):
        if unl.startswith(s):
            s2 = vc.drop_unl_tail(s)
            unl = s2 + unl[len(s):]
    return unl[3:] if unl.startswith('-->') else unl
#@+node:ekr.20150312225028.121: *7* vc.drop_unl_tail & vc.drop_unl_parent
def drop_unl_tail(self,unl):
    '''Drop the last part of the unl.'''
    return '-->'.join(unl.split('-->')[:-1])

def drop_unl_parent(self,unl):
    '''Drop the penultimate part of the unl.'''
    aList = unl.split('-->')
    return '-->'.join(aList[:-2] + aList[-1:])
#@+node:ekr.20150312225028.122: *7* vc.get_at_organizer_unls
def get_at_organizer_unls(self,p):
    '''Return the unl: lines in an @organizer: node.'''
    return [s[len('unl:'):].strip()
        for s in g.splitLines(p.b)
            if s.startswith('unl:')]

#@+node:ekr.20150312225028.123: *7* vc.relative_unl & unl
def relative_unl(self,p,root):
    '''Return the unl of p relative to the root position.'''
    vc = self
    result = []
    ivar = vc.headline_ivar
    for p in p.self_and_parents():
        if p == root:
            break
        else:
            h = getattr(p.v,ivar,p.h)
            result.append(h)
    return '-->'.join(reversed(result))

def unl(self,p):
    '''Return the unl corresponding to the given position.'''
    vc = self
    return '-->'.join(reversed([
        getattr(p.v,vc.headline_ivar,p.h)
            for p in p.self_and_parents()]))
    # return '-->'.join(reversed([p.h for p in p.self_and_parents()]))
#@+node:ekr.20150312225028.124: *7* vc.source_unl
def source_unl(self,organizer_unls,organizer_unl):
    '''Return the unl of the source node for the given organizer_unl.'''
    vc = self
    return vc.drop_all_organizers_in_unl(organizer_unls,organizer_unl)
#@+node:ekr.20150312225028.125: *7* vc.unl_tail
def unl_tail(self,unl):
    '''Return the last part of a unl.'''
    return unl.split('-->')[:-1][0]
#@+node:ekr.20150312225028.126: *4* vc.Commands
@g.command('view-pack')
def view_pack_command(event):
    c = event.get('c')
    if c and c.viewController:
        c.viewController.pack()

@g.command('view-unpack')
def view_unpack_command(event):
    c = event.get('c')
    if c and c.viewController:
        c.viewController.unpack()
        
@g.command('at-file-to-at-auto')
def at_file_to_at_auto_command(event):
    c = event.get('c')
    if c and c.viewController:
        c.viewController.convert_at_file_to_at_auto(c.p)
#@+node:ekr.20140711111623.17795: *4* class ConvertController (leoPersistence.py)
class ConvertController(object):
    '''A class to convert @file trees to @auto trees.'''

    def __init__(self, c, p):
        self.c = c
        self.pd = c.persistenceController
        self.root = p.copy()
    @others
#@+node:ekr.20140711111623.17796: *5* convert.delete_at_data_nodes
def delete_at_data_nodes(self, root):
    '''Delete all @data nodes pertaining to root.'''
    cc = self
    pd = cc.pd
    while True:
        p = pd.has_at_data_node(root)
        if not p: break
        p.doDelete()
#@+node:ekr.20140711111623.17797: *5* convert.import_from_string
def import_from_string(self, s):
    '''Import from s into a temp outline.'''
    cc = self # (ConvertController)
    c = cc.c
    # ic = c.importCommands
    root = cc.root
    language = g.scanForAtLanguage(c, root)
    ext = '.' + g.app.language_extension_dict.get(language)
    scanner = g.app.scanner_for_ext(c, ext)
    # g.trace(language,ext,scanner.__name__)
    p = root.insertAfter()
    ok = scanner(atAuto=True, c=c, parent=p, s=s)
    p.h = root.h.replace('@file', '@auto' if ok else '@@auto')
    return ok, p
#@+node:ekr.20140711111623.17798: *5* convert.run
def run(self):
    '''Convert an @file tree to @auto tree.'''
    trace = True and not g.unitTesting
    trace_s = False
    cc = self
    c = cc.c
    root, pd = cc.root, c.persistenceController
    # set the expected imported headline for all vnodes.
    t1 = time.time()
    cc.set_expected_imported_headlines(root)
    t2 = time.time()
    # Delete all previous @data nodes for this tree.
    cc.delete_at_data_nodes(root)
    t3 = time.time()
    # Ensure that all nodes of the tree are regularized.
    ok = pd.prepass(root)
    t4 = time.time()
    if not ok:
        g.es_print('Can not convert', root.h, color='red')
        if trace: g.trace(
            '\n  set_expected_imported_headlines: %4.2f sec' % (t2 - t1),
            # '\n  delete_at_data_nodes:          %4.2f sec' % (t3-t2),
            '\n  prepass:                         %4.2f sec' % (t4 - t3),
            '\n  total:                           %4.2f sec' % (t4 - t1))
        return
    # Create the appropriate @data node.
    at_auto_view = pd.update_before_write_foreign_file(root)
    t5 = time.time()
    # Write the @file node as if it were an @auto node.
    s = cc.strip_sentinels()
    t6 = time.time()
    if trace and trace_s:
        g.trace('source file...\n', s)
    # Import the @auto string.
    ok, p = cc.import_from_string(s)
    t7 = time.time()
    if ok:
        # Change at_auto_view.b so it matches p.gnx.
        at_auto_view.b = pd.at_data_body(p)
        # Recreate the organizer nodes, headlines, etc.
        pd.update_after_read_foreign_file(p)
        t8 = time.time()
        # if not ok:
            # p.h = '@@' + p.h
            # g.trace('restoring original @auto file')
            # ok,p = cc.import_from_string(s)
            # if ok:
                # p.h = '@@' + p.h + ' (restored)'
                # if p.next():
                    # p.moveAfter(p.next())
        t9 = time.time()
    else:
        t8 = t9 = time.time()
    if trace: g.trace(
        '\n  set_expected_imported_headlines: %4.2f sec' % (t2 - t1),
        # '\n  delete_at_data_nodes:          %4.2f sec' % (t3-t2),
        '\n  prepass:                         %4.2f sec' % (t4 - t3),
        '\n  update_before_write_foreign_file:%4.2f sec' % (t5 - t4),
        '\n  strip_sentinels:                 %4.2f sec' % (t6 - t5),
        '\n  import_from_string:              %4.2f sec' % (t7 - t6),
        '\n  update_after_read_foreign_file   %4.2f sec' % (t8 - t7),
        '\n  import_from_string (restore)     %4.2f sec' % (t9 - t8),
        '\n  total:                           %4.2f sec' % (t9 - t1))
    if p:
        c.selectPosition(p)
    c.redraw()
#@+node:ekr.20140711111623.17799: *5* convert.set_expected_imported_headlines
def set_expected_imported_headlines(self, root):
    '''Set v._imported_headline for every vnode.'''
    trace = False and not g.unitTesting
    cc = self
    c = cc.c
    ic = cc.c.importCommands
    language = g.scanForAtLanguage(c, root)
    ext = '.' + g.app.language_extension_dict.get(language)
    aClass = g.app.classDispatchDict.get(ext)
    scanner = aClass(importCommands=ic, atAuto=True)
    # Duplicate the fn logic from ic.createOutline.
    theDir = g.setDefaultDirectory(c, root, importing=True)
    fn = c.os_path_finalize_join(theDir, root.h)
    fn = root.h.replace('\\', '/')
    junk, fn = g.os_path_split(fn)
    fn, junk = g.os_path_splitext(fn)
    if aClass and hasattr(scanner, 'headlineForNode'):
        for p in root.subtree():
            if not hasattr(p.v, '_imported_headline'):
                h = scanner.headlineForNode(fn, p)
                setattr(p.v, '_imported_headline', h)
                if trace and h != p.h:
                    g.trace('==>', h) # p.h,'==>',h
#@+node:ekr.20140711111623.17800: *5* convert.strip_sentinels
def strip_sentinels(self):
    '''Write the file to a string without headlines or sentinels.'''
    trace = False and not g.unitTesting
    cc = self
    at = cc.c.atFileCommands
    # ok = at.writeOneAtAutoNode(cc.root,
        # toString=True,force=True,trialWrite=True)
    at.errors = 0
    at.write(cc.root,
        kind='@file',
        nosentinels=True,
        perfectImportFlag=False,
        scriptWrite=False,
        thinFile=True,
        toString=True)
    ok = at.errors == 0
    s = at.stringOutput
    if trace: g.trace('ok:', ok, 's:...\n' + s)
    return s
#@+node:ekr.20140711111623.17794: *4* pd.convert_at_file_to_at_auto
def convert_at_file_to_at_auto(self, root):
    if root.isAtFileNode():
        ConvertController(self.c, root).run()
    else:
        g.es_print('not an @file node:', root.h)
#@+node:ekr.20140131101641.15495: *4* pd.prepass & helper
def prepass(self, root):
    '''Make sure root's tree has no hard-to-handle nodes.'''
    c, pd = self.c, self
    ic = c.importCommands
    ic.tab_width = c.getTabWidth(root)
    language = g.scanForAtLanguage(c, root)
    ext = g.app.language_extension_dict.get(language)
    if not ext: return
    if not ext.startswith('.'): ext = '.' + ext
    scanner = g.app.scanner_for_ext(c, ext)
    if not scanner:
        g.trace('no scanner for', root.h)
        return True # Pretend all went well.
    # Pass 1: determine the nodes to be inserted.
    ok = True
    # parts_list = []
    for p in root.subtree():
        ok2 = pd.regularize_node(p, scanner)
        ok = ok and ok2
    return ok
#@+node:ekr.20140131101641.15496: *5* pd.regularize_node
def regularize_node(self, p, scanner):
    '''Regularize node p so that it will not cause problems.'''
    c = self.c
    ok = scanner(atAuto=True, c=c, parent=p, s=p.b)
        # The scanner is a callback returned by g.app.scanner_for_ext.
        # It must have a c argument.
    if not ok:
        g.es_print('please regularize:', p.h)
    return ok
#@+node:ekr.20180813063846.1: *3* Fast-draw branches
#@+node:ekr.20180808075509.1: *4* qtree.partialDraw & helpers (never used)
def partialDraw(self, p):

    trace = True and not g.unitTesting
    c = self.c
    if 1:
        self.drawVisible()
    else:
        first_p = c.hoistStack[-1].p if c.hoistStack else c.rootPosition()
        aList1 = self.countVisible(first_p=first_p, target_p=p)
        aList2 = self.countVisible(first_p=p, target_p=None)
        if trace:
            n1, n2 = len(aList1), len(aList2)
            g.trace('%s + %s = %s' % (n1, n2, n1+n2))
        if 1:
            # Draw everything.
            self.drawList(aList1 + aList2)
        else:
            aList = self.computeVisiblePositions(aList1, aList2, p)
            self.drawList(aList)
#@+node:ekr.20180809110957.1: *5* qtree.computeVisiblePositions
def computeVisiblePositions(self, aList1, aList2, p):
    '''
    Compute the list of *visible* positions to be drawn.
    
    This is tricky.  We don't want to scroll the screen unnecessarily.
    '''
    # Show everything if possible.
    if len(aList1) + len(aList2) <= self.size:
        return aList1 + aList2
    c = self.c
    while p.hasParent():
        p.moveToParent()
    aList = []
    for i in range(self.size):
        aList.append(p.copy())
        p.moveToVisNext(c)
        if not p:
            break
    return aList
#@+node:ekr.20180809123937.1: *5* qtree.countVisible
def countVisible(self, first_p, target_p):
    """
    Return the number of visible positions from first_p to target_p.
    """
    c = self.c
    aList, p = [first_p.copy()], first_p.copy()
    while p:
        if p == target_p:
            return aList[:-1]
        v = p.v
        # if v.isExpanded() and v.hasChildren():
        if (v.statusBits & v.expandedBit) != 0 and v.children:
            # p.moveToFirstChild()
            p.stack.append((v, p._childIndex),)
            p.v = v.children[0]
            p._childIndex = 0
            aList.append(p.copy())
            continue
        # if p.hasNext():
        parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode
        if p._childIndex + 1 < len(parent_v.children):
            # p.moveToNext()
            p._childIndex += 1
            p.v = parent_v.children[p._childIndex]
            aList.append(p.copy())
            continue
        #
        # A fast version of p.moveToThreadNext().
        # We look for a parent with a following sibling.
        while p.stack:
            # p.moveToParent()
            p.v, p._childIndex = p.stack.pop()
            # if p.hasNext():
            parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode
            if p._childIndex + 1 < len(parent_v.children):
                # p.moveToNext()
                p._childIndex += 1
                p.v = parent_v.children[p._childIndex]
                break # Found: moveToThreadNext()
        else:
            break # Not found.
        # Found moveToThreadNext()
        aList.append(p.copy())
        continue
    if target_p:
        g.trace('NOT FOUND:', target_p.h)
    return aList
#@+node:ekr.20180809115019.1: *5* qtree.drawList
def drawList(self, aList):
    
    trace = False
    c = self.c
    parents = []
    # Clear the widget.
    w = self.treeWidget
    w.clear()
    # Clear the dicts.
    self.initData()
    for p in aList:
        level = p.level()
        parent_item = w if level == 0 else parents[level-1]
        item = QtWidgets.QTreeWidgetItem(parent_item)
        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
        item.setChildIndicatorPolicy(
            item.ShowIndicator if p.hasChildren()
            else item.DontShowIndicator)
        item.setExpanded(bool(p.hasChildren() and p.isExpanded()))
        self.items.append(item)
        if trace:
            print('')
            g.trace('===== level', level, p.h)
            g.trace('parent', id(parent_item), parent_item.__class__.__name__)
            g.trace('item  ', id(item), item.__class__.__name__)
            self.print_parents(parents, 1)
        # Update parents.
        if level == 0:
            parents = []
        else:
            parents = parents[:level]
        parents.append(item)
        if trace:
            self.print_parents(parents, 2)
        # Update the dicts.  Like rememberItem.
        itemHash = self.itemHash(item)
        self.item2positionDict[itemHash] = p.copy()
        self.item2vnodeDict[itemHash] = p.v
        self.position2itemDict[p.key()] = item
        d = self.vnode2itemsDict
        v = p.v
        aList = d.get(v, [])
        aList.append(item)
        d[v] = aList
        # Enter the headline.
        item.setText(0, p.h)
        # Set current item.
        if p == c.p:
            w.setCurrentItem(item)
#@+node:ekr.20180809111725.1: *5* qtree.printParents
def print_parents(self, parents, tag):
    print(tag)
    g.printObj([
        '%10s %s' % (id(z), z.__class__.__name__)
            for z in parents])
#@+node:ekr.20180810060655.1: *4* test vieldVisible
g.cls()
import time
tree = c.frame.tree
if 1: # works
    for p in c.all_positions():
        p.expand()
elif 1:
    for p in c.all_positions():
        p.v.expandedPositions = []
    for p in c.all_positions():
        p.v.expand()
        p.v.expandedPositions.append(p.copy())
else: # Doesn't work
    for v in c.all_nodes():
        v.expand()
    c.redraw() # This would be wrong.
if 1:
    t1 = time.clock()
    for i in range(1):
        aList1 = [z.copy() for z in tree.slowYieldVisible(c.rootPosition())]
    t2 = time.clock()
    print('slow: %6.3f' % (t2-t1))
if 1:
    t1 = time.clock()
    for i in range(1):
        aList2 = [z.copy() for z in tree.yieldVisible(c.rootPosition())]
    t2 = time.clock()
    print('fast: %6.3f' % (t2-t1))
v1 = [z.v for z in aList1]
v2 = [z.v for z in aList2]
try:
    i = 0
    while i < min(len(aList1), len(aList2)) and aList1[i] == aList2[i]:
        # print(i, aList1[i].h)
        i += 1
    if i == len(aList1) == len(aList2):
        print('OK')
    else:
        print('FAIL', i, len(aList1), len(aList2))
    assert aList1 == aList2, (len(aList1), len(aList2))
    assert v1 == v2, (len(v1), len(v2))
finally:
    for v in c.all_nodes():
        v.contract()
    c.redraw()
#@+node:ekr.20180810111515.1: *4* benchmark
import time
t1 = time.clock()
c.expandAllHeadlines()
t2 = time.clock()
print('expand: %5.2f sec' % (t2-t1))
t3 = time.clock()
tree = c.frame.tree
w = tree.treeWidget
n = 0
for p in tree.yieldVisible(c.rootPosition()):
    n += 1
    # c.selectPosition(p)
    item = tree.position2itemDict.get(c.p.key())
    if item:
        w.setCurrentItem(item)
t4 = time.clock()
print('%s nodes in %5.2f sec' % (n, t4-t3))
#@+node:ekr.20110605121601.17879: *4* qtree.rememberItem
def rememberItem(self, p, item):

    v = p.v
    # Update position dicts.
    itemHash = self.itemHash(item)
    self.position2itemDict[p.key()] = item
    self.item2positionDict[itemHash] = p.copy() # was item
    # Update item2vnodeDict.
    self.item2vnodeDict[itemHash] = v # was item
    # Update vnode2itemsDict.
    d = self.vnode2itemsDict
    aList = d.get(v, [])
    if item in aList:
        g.trace('*** ERROR *** item already in list: %s, %s' % (item, aList))
    else:
        aList.append(item)
    d[v] = aList
#@+node:ekr.20120219154958.10488: *4* LM.initFocusAndDraw (not used)
def initFocusAndDraw(self, c, fileName):

    def init_focus_handler(timer, c=c, p=c.p):
        '''Idle-time handler for initFocusAndDraw'''
        c.initialFocusHelper()
        c.outerUpdate()
        timer.stop()

    # This must happen after the code in getLeoFile.
    timer = g.IdleTime(init_focus_handler, delay=0.1, tag='getLeoFile')
    if timer:
        timer.start()
    else:
        # Default code.
        c.selectPosition(c.p)
        c.initialFocusHelper()
        c.k.showStateAndMode()
        c.outerUpdate()
#@+node:ekr.20150312225028.6: ** class LogManager (not used yet)
class LogManager:

    '''A class to handle the global log, and especially
    switching the log from commander to commander.'''

    def __init__ (self):

        trace = (False or g.trace_startup) and not g.unitTesting
        if trace: g.es_debug('(LogManager)')

        self.log = None             # The LeoFrame containing the present log.
        self.logInited = False      # False: all log message go to logWaiting list.
        self.logIsLocked = False    # True: no changes to log are allowed.
        self.logWaiting = []        # List of messages waiting to go to a log.
        self.printWaiting = []      # Queue of messages to be sent to the printer.
        self.signon_printed = False # True: the global signon has been printed.

    @others
#@+node:ekr.20150312225028.7: *3* LogM.setLog, lockLog, unlocklog
def setLog (self,log):

    """set the frame to which log messages will go"""

    # print("app.setLog:",log,g.callers())
    if not self.logIsLocked:
        self.log = log

def lockLog(self):
    """Disable changes to the log"""
    self.logIsLocked = True

def unlockLog(self):
    """Enable changes to the log"""
    self.logIsLocked = False
#@+node:ekr.20150312225028.8: *3* LogM.writeWaitingLog
def writeWaitingLog (self,c):
    '''Write all waiting lines to the log.'''
    trace = True
    lm = self
    if trace:
        # Do not call g.es, g.es_print, g.pr or g.trace here!
        print('** writeWaitingLog','silent',g.app.silentMode,c.shortFileName())
        # print('writeWaitingLog',g.callers())
        # import sys ; print('writeWaitingLog: argv',sys.argv)
    if not c or not c.exists:
        return
    if g.unitTesting:
        lm.printWaiting = []
        lm.logWaiting = []
        g.app.setLog(None) # Prepare to requeue for other commanders.
        return
    table = [
        ('Leo Log Window','red'),
        (g.app.signon,'black'),
        (g.app.signon2,'black'),
    ]
    table.reverse()
    c.setLog()
    lm.logInited = True # Prevent recursive call.
    if not lm.signon_printed:
        lm.signon_printed = True
        if not g.app.silentMode:
            print('')
            print('** isPython3: %s' % g.isPython3)
            if not g.enableDB:
                print('** caching disabled')
            print(g.app.signon)
            print(g.app.signon2)
    if not g.app.silentMode:
        for s in lm.printWaiting:
            print(s)
    lm.printWaiting = []
    if not g.app.silentMode:
        for s,color in table:
            lm.logWaiting.insert(0,(s+'\n',color),)
        for s,color in lm.logWaiting:
            g.es('',s,color=color,newline=0)
                # The caller must write the newlines.
    lm.logWaiting = []
    # Essential when opening multiple files...
    lm.setLog(None)
#@+node:ekr.20150526115312.1: ** compare_ast (can hang!?!)
# http://stackoverflow.com/questions/3312989/
# elegant-way-to-test-python-asts-for-equality-not-reference-or-object-identity

def compare_ast(node1, node2):

    trace = False and not g.unitTesting

    def fail(node1, node2, tag):
        '''Report a failed mismatch in the beautifier. This is a bug.'''
        name1 = node1.__class__.__name__
        name2 = node2.__class__.__name__
        format = 'compare_ast failed: %s: %s %s %r %r'
        if name1 == 'str':
            print(format % (tag, name1, name2, node1, node2))
        elif name1 == 'Str':
            print(format % (tag, name1, name2, node1.s, node2.s))
        elif 1:
            format = 'compare_ast failed: %s: %s %s\n%r\n%r'
            print(format % (tag, name1, name2, node1, node2))
        else:
            format = 'compare_ast failed: %s: %s %s\n%r\n%r\n%r %r'
            attr1 = getattr(node1, 'lineno', '<no lineno>')
            attr2 = getattr(node2, 'lineno', '<no lineno>')
            print(format % (tag, name1, name2, node1, node2, attr1, attr2))

    # pylint: disable=unidiomatic-typecheck
    if type(node1) != type(node2):
        if trace: fail(node1, node2, 'type mismatch')
        return False
    # The types of node1 and node2 match. Recursively compare components.
    if isinstance(node1, ast.AST):
        for kind, var in vars(node1).items():
            if kind not in ('lineno', 'col_offset', 'ctx'):
                var2 = vars(node2).get(kind) # Bug fix: 2016/05/16.
                if not compare_ast(var, var2):
                    if trace: fail(var, var2, 'AST subnode mismatch')
                    return False
        return True
    elif isinstance(node1, list):
        if len(node1) != len(node2):
            if trace: fail(node1, node2, 'list len mismatch')
            return False
        for i in range(len(node1)):
            if not compare_ast(node1[i], node2[i]):
                if trace: fail(node1, node2, 'list element mismatch')
                return False
        return True
    elif node1 != node2:
        if trace: fail(node1, node2, 'node mismatch')
        return False
    else:
        return True
#@+node:ekr.20180826065640.1: ** vr.embed_pyplot_widget (not used)
def embed_pyplot_widget(self):

    pc = self
    c = pc.c
    # Careful: we may be unit testing.
    splitter = c.free_layout.get_top_splitter()
    if not splitter:
        return
    if not pc.pyplot_canvas:

        # TODO Create the widgets.
        w = None
        ### Ref
        # pc.gs = QtWidgets.QGraphicsScene(splitter)
        # pc.gv = QtWidgets.QGraphicsView(pc.gs)
        # w = pc.gv.viewport() # A QWidget
        # Embed the widgets.
        pc.pyplot_canvas = w

        def delete_callback():
            pc.pyplot_canvas.deleteLater()
            pc.pyplot_canvas = None

    if pc.pyplot_canvas:
        pc.embed_widget(w, delete_callback=delete_callback)
#@+node:ekr.20190412154124.1: ** COPY pyzo_shims.py
@first # -*- coding: utf-8 -*-
'''Shims to adapt pyzo classes to Leo.'''
<< copyright notices >>
import leo.core.leoGlobals as g
import sys
sys.argv.append('--pyzo')
    # To retain g.pyzo and other global switches.
if g.pyzo_trace_imports:
    print('\npyzo_shims.py')
<< non-pyzo imports (pyzo_shims.py) >>

old_loadFile = None
    # Set by monkey_patch.
    # Save a permanent reference.

# Be explicit about where everything comes from...
import pyzo
import pyzo.core.editor
import pyzo.core.main
import pyzo.core.splash
import pyzo.util

@others
#@+node:ekr.20190412154124.2: *3* << copyright notices >>
#
# Portions of this file are based on pyzo's source code:
# Copyright (C) 2013-2018 by Almar Klein.
#
# Search for the above copyright to see the relevant code.
#@+node:ekr.20190412154124.3: *3*  << non-pyzo imports (pyzo_shims.py) >>
import os
import sys
import time
from leo.core.leoQt import QtCore, QtWidgets
try:
    from PyQt5.QtCore import pyqtSignal as Signal
except ImportError:
    try:
        from PyQt4.QtCore import pyqtSignal as Signal
    except ImportError:
        Signal = g.TracingNullObject

#@+node:ekr.20190412154124.4: *3*  function: createEditor (from editor.py)
# Imports: editor.py

# import os, sys
# import re, codecs

# from pyzo.util.qt import QtCore, QtGui, QtWidgets
# qt = QtGui

# from pyzo.codeeditor import Manager
# from pyzo.core.menu import EditorContextMenu
# from pyzo.core.baseTextCtrl import BaseTextCtrl, normalizePath
# from pyzo.core.pyzoLogging import print  # noqa
# import pyzo

from pyzo.core.editor import determineEncoding, determineLineEnding, determineIndentation
from pyzo.core.editor import PyzoEditor
from pyzo.codeeditor import Manager

def createEditor(parent, filename=None):
    """ Tries to load the file given by the filename and
    if succesful, creates an editor instance to put it in,
    which is returned.
    If filename is None, an new/unsaved/temp file is created.
    """
    
    if g.pyzo_trace:
        g.pr('createEditor: %r' % filename)

    if filename is None:
        # Increase counter
        global newFileCounter
        newFileCounter  += 1
        # Create editor
        editor = PyzoEditor(parent)
        editor.document().setModified(True)
        # Set name
        editor._name = "<tmp {}>".format(newFileCounter)
    elif g.pyzo_outline_tab and g.pyzo and g.pyzo_patch and filename.endswith('.leo'):
        from leo.core.pyzo_shims import OutlineEditorShim
        << createEditor patch >>
        return editor
    else:
        # check and normalize
        if not os.path.isfile(filename):
            raise IOError("File does not exist '%s'." % filename)
        # load file (as bytes)
        with open(filename, 'rb') as f:
            bb = f.read()
            f.close()
        # convert to text, be gentle with files not encoded with utf-8
        encoding = determineEncoding(bb)
        text = bb.decode(encoding,'replace')

        # process line endings
        lineEndings = determineLineEnding(text)

        # if we got here safely ...

        # create editor and set text
        editor = PyzoEditor(parent) # showlinenumbers=False)
        editor.setPlainText(text)
        editor.lineEndings = lineEndings
        editor.encoding = encoding
        editor.document().setModified(False)

        # store name and filename
        editor._filename = filename
        editor._name = os.path.split(filename)[1]

        # process indentation
        indentWidth = determineIndentation(text)
        if indentWidth == -1: #Tabs
            editor.setIndentWidth(pyzo.config.settings.defaultIndentWidth)
            editor.setIndentUsingSpaces(False)
        elif indentWidth:
            editor.setIndentWidth(indentWidth)
            editor.setIndentUsingSpaces(True)

    if editor._filename:
        editor._modifyTime = os.path.getmtime(editor._filename)

    # Set parser
    if editor._filename:
        ext = os.path.splitext(editor._filename)[1]
        parser = Manager.suggestParser(ext, text)
        editor.setParser(parser)
    else:
        # todo: rename style -> parser
        editor.setParser(pyzo.config.settings.defaultStyle)

    # return
    return editor
#@+node:ekr.20190412154124.5: *4* << createEditor patch >>
# check and normalize
if not os.path.isfile(filename):
    raise IOError("File does not exist '%s'." % filename)
#
# load file (as bytes)
with open(filename, 'rb') as f:
    bb = f.read()
    f.close()
    
# convert to text, be gentle with files not encoded with utf-8
encoding = determineEncoding(bb)
text = bb.decode(encoding,'replace')

# process line endings
lineEndings = determineLineEnding(text)

# if we got here safely ...

# create editor and set text
### editor = PyzoEditor(parent)
editor = OutlineEditorShim(filename, parent)
editor.setPlainText(text)
# g.trace('len(text)', len(text))

editor.lineEndings = lineEndings
editor.encoding = encoding
editor.document().setModified(False)

# store name and filename
# Now done in OutlineEditorShim.
    # editor._filename = filename
    # editor._name = os.path.split(filename)[1]

# process indentation
###
    # indentWidth = determineIndentation(text)
    # if indentWidth == -1: #Tabs
        # editor.setIndentWidth(pyzo.config.settings.defaultIndentWidth)
        # editor.setIndentUsingSpaces(False)
    # elif indentWidth:
        # editor.setIndentWidth(indentWidth)
        # editor.setIndentUsingSpaces(True)

if editor._filename:
    editor._modifyTime = os.path.getmtime(editor._filename)

# Set parser
if 0:
    if editor._filename:
        ### ext = os.path.splitext(editor._filename)[1]
        ext = '.py'
        parser = Manager.suggestParser(ext, text)
        editor.setParser(parser)
    else:
        # todo: rename style -> parser
        editor.setParser(pyzo.config.settings.defaultStyle)
#@+node:ekr.20190412154124.6: *3*  function: loadFile (pyzo_shims.py)
# This probably isn't the place to patch pyzo.

def loadFile(self, filename, updateTabs=True):
    '''
    A monkey-patched replacement for pyzo.core.editorTabs.EditorTabs.loadFile.
    '''
    # Not used, and will probably never be used.
    print('----- patched loadFile: filename:', filename)
    if filename.endswith('leo'):
        print('----- ignoring .leo file')
        return None
    try:
        return old_loadFile(self, filename, updateTabs)
    except Exception:
        g.es_exception()
        return None
#@+node:ekr.20190412154124.7: *3* class ConfigShim
new_config = True
    # Works when False.
    
config_shim_seen = {}
    # Keys are bunches.

try:  # pragma: no cover
    from collections import OrderedDict as _dict
except ImportError:
    _dict = dict
    
from pyzo.util.zon import Dict

# config_base = _dict if new_config else Dict

config_base = _dict if new_config else Dict
    # The old code needs _dict, not dict.

class ConfigShim(config_base):
    
    def __init__(self):
        config_base.__init__(self)
        g.pr('\n===== ConfigShim: new_config: %s\n' % new_config)
        self.setObjectName('ConfigShim')
    
    if new_config:
        << define bunch settings >>
        << new config methods >>
    else:
        << old config methods >>

    @others
#@+node:ekr.20190412154124.8: *4* << define bunch settings >>
# '(\w+)': With: \1 = 

advanced = g.Bunch(
    autoCompDelay=200,
    fileExtensionsToLoadFromDir='py,pyw,pyx,txt,bat',
    find_autoHide_timeout=10,
    homeAndEndWorkOnDisplayedLine=0,
    shellMaxLines=10000,
    titleText='{fileName} ({fullPath}) - Interactive Editor for Python',
)

settings = g.Bunch(   
    allowFloatingShell=0,
    autoCallTip=1,
    autoClose_Brackets=1,
    autoClose_Quotes=1,
    autoComplete=1,
    autoComplete_acceptKeys='Tab',
    autoComplete_caseSensitive=0,
    autoComplete_fillups='\n',
    autoComplete_keywords=1,
    autoIndent=1,
    changeDirOnFileExec=0,
    defaultIndentUsingSpaces=1,
    defaultIndentWidth=4,
    defaultLineEndings='CRLF',
    defaultStyle='python',
    justificationWidth=70,
    language='English (US)',
    removeTrailingWhitespaceWhenSaving=0,
)

shellConfigs2 = [
    g.Bunch(
        argv = '',
        environ = '',
        exe = 'c:\\anaconda3\\python.exe',
        gui = 'auto',
        ipython = 'yes',
        name = 'Python',
        projectPath = '',
        pythonPath = '',
        scriptFile = '',
        startDir = '',
        startupScript = '',
        )
]

shortcuts2 = g.bunch(
    << define bunch shortcuts2 >>
)

state = g.Bunch(
    editorState2=[
        << define editorState2 >>
    ],
    find_autoHide=1,
    find_matchCase=0,
    find_regExp=0,
    find_show=0,
    find_wholeWord=1,
    loadedTools=['pyzofilebrowser', 'pyzologger', 'pyzosourcestructure'],
    newUser=1,
    windowGeometry='AdnQywACAAAAAAGjAAAA2AAABv0AAANWAAABqwAAAPcAAAb1AAADTgAAAAAAAAAAB4A=\n',
    windowState=(
        'AAAA/wAAAAD9AAAAAgAAAAAAAACeAAACRPwCAAAAAfwAAAAUAAACRAAAAYgA/////AIAAAAC+wAA\n'
        'AB4AcAB5AHoAbwBmAGkAbABlAGIAcgBvAHcAcwBlAHIBAAAAFAAAAWEAAAEIAP////sAAAAmAHAA\n'
        'eQB6AG8AcwBvAHUAcgBjAGUAcwB0AHIAdQBjAHQAdQByAGUBAAABewAAAN0AAAB6AP///wAAAAEA\n'
        'AAGEAAACRPwCAAAABvsAAAAMAHMAaABlAGwAbABzAQAAABQAAAD/AAAAcwD////7AAAAFABwAHkA\n'
        'egBvAGwAbwBnAGcAZQByAQAAARkAAAE/AAAAWQD////7AAAAGgBwAHkAegBvAHcAbwByAGsAcwBw\n'
        'AGEAYwBlAAAAAT4AAAEaAAAAAAAAAAD7AAAAJgBwAHkAegBvAGkAbgB0AGUAcgBhAGMAdABpAHYA\n'
        'ZQBoAGUAbABwAAAAAnkAAAB6AAAAAAAAAAD7AAAAIgBwAHkAegBvAGgAaQBzAHQAbwByAHkAdgBp\n'
        'AGUAdwBlAHIAAAACGgAAAVsAAAAAAAAAAPsAAAAcAHAAeQB6AG8AdwBlAGIAYgByAG8AdwBzAGUA\n'
        'cgAAAAKwAAAAxQAAAAAAAAAAAAADHQAAAkQAAAAEAAAABAAAAAgAAAAI/AAAAAA=\n'
    )
)

tools = g.Bunch(
    pyzofilebrowser = g.Bunch(),
    pyzofilebrowser2 = g.Bunch(
        expandedDirs = ['c:\\apps\\pyzo\\source\\pyzo'],
        nameFilter = '!*.pyc',
        path = 'c:\\apps\\pyzo\\source\\pyzo',
        searchMatchCase = 0,
        searchRegExp = 0,
        searchSubDirs = 1,
        starredDirs =[
            g.Bunch(
                addToPythonpath = 0,
                name = 'Pyzo sources',
                path = 'c:\\apps\\pyzo\\source',
            )
        ]
    ),
    pyzohistoryviewer = g.Bunch(),
    pyzointeractivehelp = g.Bunch(
        fontSize = 14,
        noNewlines = 1,
        smartNewlines = 1
    ),
    pyzologger = g.Bunch(),
    pyzosourcestructure = g.Bunch(
        level = 1,
        showTypes = ['class', 'def', 'cell', 'todo'],
    ),
    pyzowebbrowser = g.Bunch(
        bookMarks = [
            'docs.python.org',
            'scipy.org',
            'doc.qt.nokia.com/4.5/',
            'pyzo.org',
        ],
        zoomFactor = 1.0,
    ),
    pyzoworkspace = g.Bunch(
        hideTypes = [],
        typeTranslation = g.Bunch(
            builtin_function_or_method = 'function',
            method = 'function'
        )
    )
) 

view = g.Bunch(
    autoComplete_popupSize=[300, 100],
    codeFolding=0,
    doBraceMatch=1,
    edgeColumn=80,
    fontname='DejaVu Sans Mono',
    highlightCurrentLine=1,
    highlightMatchingBracket=1,
    qtstyle='fusion',
    showIndentationGuides=1,
    showLineEndings=0,
    showStatusbar=0,
    showWhitespace=0,
    showWrapSymbols=0,
    tabWidth=4,
    wrap=1,
    zoom=2
)
#@+node:ekr.20190412154124.9: *5* << define bunch shortcuts2 >>
edit__comment = 'Ctrl+R,',
edit__copy = 'Ctrl+C,Ctrl+Insert',
edit__cut = 'Ctrl+X,Shift+Delete',
edit__dedent = 'Shift+Tab,',
edit__delete_line = 'Ctrl+D,',
edit__duplicate_line = 'Ctrl+Shift+D,',
edit__find_next = 'Ctrl+G,F3',
edit__find_or_replace = 'Ctrl+F,',
edit__find_previous = 'Ctrl+Shift+G,Shift+F3',
edit__find_selection = 'Ctrl+F3,',
edit__find_selection_backward = 'Ctrl+Shift+F3,',
edit__indent = 'Tab,',
edit__justify_commentdocstring = 'Ctrl+J,',
edit__paste = 'Ctrl+V,Shift+Insert',
edit__paste_and_select = 'Ctrl+Shift+V',
edit__redo = 'Ctrl+Y,',
edit__select_all = 'Ctrl+A,',
edit__toggle_breakpoint = 'Ctrl+B,',
edit__uncomment = 'Ctrl+T,',
edit__undo = 'Ctrl+Z,',
file__close = 'Ctrl+W,',
file__new = 'Ctrl+N,',
file__open = 'Ctrl+O,',
file__save = 'Ctrl+S,',
run__execute_cell = 'Ctrl+Return,Ctrl+Enter',
run__execute_cell_and_advance = 'Ctrl+Shift+Return,Ctrl+Shift+Enter',
run__execute_file = 'Ctrl+E,F5',
run__execute_main_file = 'Ctrl+M,F6',
run__execute_selection = 'Alt+Return,F9',
run__execute_selection_and_advance = 'Shift+F9,Shift+Alt+Return',
run__run_file_as_script = 'Ctrl+Shift+E,Ctrl+F5',
run__run_main_file_as_script = 'Ctrl+Shift+M,Ctrl+F6',
shell__clear_screen = 'Ctrl+L,',
shell__close = 'Alt+K,',
shell__create_shell_1_ = 'Ctrl+1,',
shell__create_shell_2_ = 'Ctrl+2,',
shell__create_shell_3_ = 'Ctrl+3,',
shell__create_shell_4_ = 'Ctrl+4,',
shell__create_shell_5_ = 'Ctrl+5,',
shell__create_shell_6_ = 'Ctrl+6,',
shell__create_shell_7_ = 'Ctrl+7,',
shell__create_shell_8_ = 'Ctrl+8,',
shell__interrupt = 'Ctrl+I,Meta+C',
shell__postmortem_debug_from_last_traceback = 'Ctrl+P,',
shell__restart = 'Ctrl+K,',
shell__terminate = 'Ctrl+Shift+K,',
view__select_editor = 'Ctrl+9,F2',
view__select_previous_file = 'Ctrl+Tab,',
view__select_shell = 'Ctrl+0,F1',
view__zooming__zoom_in = 'Ctrl+=,Ctrl++',
view__zooming__zoom_out = 'Ctrl+-,',
view__zooming__zoom_reset = 'Ctrl+\\,'
#@+node:ekr.20190412154124.10: *5* << define editorState2 >>
[
    'C:\\apps\\pyzo\\source\\pyzo\\codeeditor\\highlighter.py',
    3279,
    96
],
[
    'C:\\apps\\pyzo\\source\\pyzo\\core\\editorTabs.py',
    22913,
    693
],
[
    'C:\\apps\\pyzo\\source\\pyzo\\codeeditor\\highlighter.py',
    'hist'
],
[
    'C:\\apps\\pyzo\\source\\pyzo\\core\\editorTabs.py',
    'hist'
]
#@+node:ekr.20190412154124.11: *4* << new config methods >>
@others
#@+node:ekr.20190412154124.12: *5* ConfigShim.__repr__
def __repr__(self):
    
    return 'ConfigShim'
    # return g.obj2string(self)
        # Can't do this: it calls repr!
#@+node:ekr.20190412154124.13: *5* ConfigShim.__getattribute__ 
def __getattribute__(self, key):
    '''The usual shinanigans...'''
    ### return object.__getattribute__(self, key)
    try:
        val = object.__getattribute__(self, key)
    except AttributeError:
        if key in self:
            val = self[key]
        else:
            raise
    if key not in config_shim_seen:
        config_shim_seen [key] = True
        g.pr('\n===== ConfigShim.__getattribute__', key, val)
    return val
#@+node:ekr.20190412154124.14: *5* ConfigShim.__setattr__ (not used)
# def __setattr__(self, key, val):
    # if key in Dict.__reserved_names__:
        # # Either let OrderedDict do its work, or disallow
        # if key not in Dict.__pure_names__:
            # return _dict.__setattr__(self, key, val)
        # else:
            # raise AttributeError('Reserved name, this key can only ' +
                                 # 'be set via ``d[%r] = X``' % key)
    # else:
        # # if isinstance(val, dict): val = Dict(val) -> no, makes a copy!
        # self[key] = val
#@+node:ekr.20190412154124.15: *4* << old config methods >>
@others
#@+node:ekr.20190412154124.16: *5* ConfigShim.__repr__
def __repr__(self):

    from pyzo.util.zon import isidentifier
        # Changed import.
    identifier_items = []
    nonidentifier_items = []
    for key, val in self.items():
        if isidentifier(key):
            identifier_items.append('%s=%r' % (key, val))
        else:
            nonidentifier_items.append('(%r, %r)' % (key, val))
    if nonidentifier_items:
        return 'Dict([%s], %s)' % (', '.join(nonidentifier_items),
                                   ', '.join(identifier_items))
    else:
        return 'Dict(%s)' % (', '.join(identifier_items))
#@+node:ekr.20190412154124.17: *5* ConfigShim.__getattribute__
def __getattribute__(self, key):
    try:
        ### return object.__getattribute__(self, key)
        val = object.__getattribute__(self, key)
        if False and key not in ('advanced', 'shortcuts2', 'settings'):
            # g.pr('===== LeoPyzoConfig 1: %r: %r' % (key, val))
            g.pr('===== LeoPyzoConfig 1: %r' % key)
        return val
    except AttributeError:
        if key in self:
            if False and key not in ('advanced', 'shortcuts2', 'settings'):
                # g.pr('===== LeoPyzoConfig 1: %r: %r' % (key, g.truncate(self[key], 50)))
                g.pr('===== LeoPyzoConfig 2: %r' % key)
            return self[key]
        else:
            raise
#@+node:ekr.20190412154124.18: *5* ConfigShim.__setattr__
def __setattr__(self, key, val):
    if key in Dict.__reserved_names__:
        # Either let OrderedDict do its work, or disallow
        if key not in Dict.__pure_names__:
            return _dict.__setattr__(self, key, val)
        else:
            raise AttributeError('Reserved name, this key can only ' +
                                 'be set via ``d[%r] = X``' % key)
    else:
        # if isinstance(val, dict): val = Dict(val) -> no, makes a copy!
        self[key] = val
#@+node:ekr.20190412154124.19: *3* class MainWindowShim (pyzo.core.main.MainWindow)
class MainWindowShim(pyzo.core.main.MainWindow):
    
    << MainWindowShim switches >>

    @others
#@+node:ekr.20190412154124.20: *4* << MainWindowShim switches >>
initial_draw = False
    # True: do an initial draw. Works either way.
use_shell = False
    # There is no great flash when use_shell is True.
use_menu = False
#
# The shell never warms up if there are no menus.
# So for now just force use_menu to True.
if use_shell and not use_menu:
    g.pr('\nMainWindowShim: use_shell sets use_menu = True\n')
    use_menu = True
#@+node:ekr.20190412154124.21: *4* MainWindowShim.__init__
def __init__(self, parent=None, locale=None):
    '''
    Important: do *all* inits here.  Do *not* call MainWindow.__init__.
    
    This allows us complete control over all aspects of the startup process.

    This method is based on pyzo code
    Copyright (C) 2013-2018 by Almar Klein.
    
    '''
    #
    # pylint: disable=non-parent-init-called, super-init-not-called
    QtWidgets.QMainWindow.__init__(self, parent)
        #
        # Do **not** call MainWindow.__init__: it calls _populate!
        #
    self.setObjectName('MainWindowShim')
    self.monkey_patch_leo()
    pyzo.loadConfig()
        # To be replaced by LeoPyzoConfig.loadConfig.
    self._closeflag = 0
        # Used during closing/restarting
    #
    # Init window title and application icon
    # Set title to something nice. On Ubuntu 12.10 this text is what
    # is being shown at the fancy title bar (since it's not properly
    # updated)
    self.setWindowTitle('Leo Main Window')
    pyzo.core.main.loadAppIcons()
    g.app.gui.attachLeoIcon(self)
    #
    # Restore window geometry before drawing for the first time.
    self.resize(800, 600) # default size
    self.restoreGeometry()
    #
    # This just slows down the initial draw.
        # self.setCentralWidget(SplashShim(parent))
    #
    # These do nothing, even when use_shell is True.
        # self.setStyleSheet("QMainWindow { background-color: #268bd2;}")
        # self.setStyleSheet("QMainWindow { background-color: red;}")
    #
    # Show empty window and disable updates for a while
    if self.initial_draw:
        self.show()
        self.paintNow()
    self.setUpdatesEnabled(False)
    #
    # Set locale of main widget, for translate.
    if locale:
        self.setLocale(locale)
    #
    # Store myself
    pyzo.main = self
    #
    # Init dockwidget settings
    self.setTabPosition(QtCore.Qt.AllDockWidgetAreas, QtWidgets.QTabWidget.South)
    self.setDockOptions(
            QtWidgets.QMainWindow.AllowNestedDocks |
            QtWidgets.QMainWindow.AllowTabbedDocks
            #|  QtWidgets.QMainWindow.AnimatedDocks
        )
    #
    # Set window atrributes
    self.setAttribute(QtCore.Qt.WA_AlwaysShowToolTips, True)
    #
    # Load pyzo icons and fonts.
    pyzo.core.main.loadIcons()
    pyzo.core.main.loadFonts()
    #
    # Set qt style and test success
    self.setQtStyle(None) # None means init!
    #    
    # Populate the window (imports more code)
    self._populate()
    #
    # Revert to normal background, and enable updates
    self.setStyleSheet('')
        # Required.
    self.setUpdatesEnabled(True)
    #
    # Restore window state, force updating, and restore again
    self.restoreState()
    if not self.initial_draw:
        self.show()
    self.paintNow()
    self.restoreState()
    #
    # Create new shell config if there is None
    if not pyzo.config.shellConfigs2:
        from pyzo.core.kernelbroker import KernelInfo
        pyzo.config.shellConfigs2.append( KernelInfo() )
    #
    # EKR: Set background.
    if getattr(pyzo.config.settings, 'dark_theme', None):
        bg = getattr(pyzo.config.settings, 'dark_background', '#657b83')
            # Default: solarized base00
        try:
            self.setStyleSheet("background: %s" % bg) 
        except Exception:
            g.pr('oops: MainWindow.__init__')
    #
    # Put the focus on editor
    e = pyzo.editors.getCurrentEditor()
    if e is not None:
        e.setFocus()
    #
    # Handle any actions
    pyzo.core.commandline.handle_cmd_args()
#@+node:ekr.20190412154124.22: *4* MainWindowShim._populate (5 shims)
def _populate(self):
    '''
    This method is based on pyzo code
    Copyright (C) 2013-2018 by Almar Klein.
    '''
    
    trace = False and g.pyzo_trace_imports

    # Delayed imports, exactly as in MainWindow._populate.
    if trace:
        g.pr('\n===== MainWindowShim._populate\n')
    from pyzo.core.editorTabs import EditorTabs
    from pyzo.core.shellStack import ShellStackWidget
    from pyzo.core import codeparser
    from pyzo.core.history import CommandHistory
    from pyzo.tools import ToolManager
    if trace:
        g.pr('\n===== MainWindowShim._populate: end of delayed imports\n')
        g.pr('initial_draw:', self.initial_draw)
        g.pr('    use_menu:', self.use_menu)
        g.pr('   use_shell:', self.use_shell)
    #
    # Instantiate tool manager
    pyzo.toolManager = ToolManager()
    #
    # Instantiate and start source-code parser
    if pyzo.parser is None:
        pyzo.parser = codeparser.Parser()
        pyzo.parser.start()
    #
    # Create editor stack and make the central widget
    pyzo.editors = EditorTabs(self)
    self.setCentralWidget(pyzo.editors)
    #
    # Create floater for shell
    self._shellDock = dock = QtWidgets.QDockWidget(self)
    if pyzo.config.settings.allowFloatingShell:
        dock.setFeatures(dock.DockWidgetMovable | dock.DockWidgetFloatable)
    else:
        dock.setFeatures(dock.DockWidgetMovable)
    dock.setObjectName('shells')
    dock.setWindowTitle('Shells')
    self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
    #
    # Create shell stack
    if self.use_shell:
        # Disabling the shell works.
        pyzo.shells = ShellStackWidget(self)
        dock.setWidget(pyzo.shells)
        pyzo.shells.menu = g.TracingNullObject(tag='pyzo.shells.menu')
            # To suppress menu events.
    else:
        pyzo.shells = g.TracingNullObject(tag='pyzo.shells')
    #
    # Initialize command history
    pyzo.command_history = CommandHistory('command_history.py')
    #
    # Create the default shell when returning to the event queue
    if self.use_shell:
        pyzo.core.main.callLater(pyzo.shells.addShell)
    #
    # Create statusbar
    if pyzo.config.view.showStatusbar:
        pyzo.status = self.statusBar()
    else:
        pyzo.status = None
        self.setStatusBar(None)
    #
    # Create the menu.
    if self.use_menu:
        from pyzo.core import menu
        pyzo.keyMapper = menu.KeyMapper()
        assert not isinstance(pyzo.keyMapper, (g.TracingNullObject, g.NullObject))
            # This should not be a Shim.
        menu.buildMenus(self.menuBar())
        pyzo.editors.addContextMenu()
        pyzo.shells.addContextMenu()
    else:
        # Shim:
        pyzo.shells = g.TracingNullObject(tag='pyzo.shells')
        pyzo.keyMapper = g.TracingNullObject(tag='pyzo.keyMapper')
        
        from pyzo.core.shellStack import ShellStackWidget
        
        def null_menu_callback(*args, **kwargs):
            pass # g.trace(args, kwargs)
            
        # Apparently, doing nothing prevents the Shell from warming up.
        # For now, use_shell sets use_menu.
        assert not self.use_shell
        g.funcToMethod(null_menu_callback, ShellStackWidget, name='onShellStateChange')
        g.funcToMethod(null_menu_callback, ShellStackWidget, name='onShellDebugStateChange')
    #
    # Load tools
    if pyzo.config.state.newUser and not pyzo.config.state.loadedTools:
        pyzo.toolManager.loadTool('pyzosourcestructure')
        pyzo.toolManager.loadTool('pyzofilebrowser', 'pyzosourcestructure')
    elif pyzo.config.state.loadedTools:
        for toolId in pyzo.config.state.loadedTools:
            pyzo.toolManager.loadTool(toolId)
#@+node:ekr.20190412154124.23: *4* MainWindowShim.closeEvent (traces)
def closeEvent(self, event):
    """ Override close event handler. """
    import pyzo.core.commandline as commandline
    
    g.pr('\nMainWindowShim.closeEvent 1')
    
    t1 = time.clock()

    # Are we restaring?
    ### restarting = time.time() - self._closeflag < 1.0

    # Save settings
    pyzo.saveConfig()
    pyzo.command_history.save()

    # Stop command server
    commandline.stop_our_server()

    # Proceed with closing...
    result = pyzo.editors.closeAll()
    if not result:
        self._closeflag = False
        event.ignore()
        return
    else:
        self._closeflag = True
        
    t2 = time.clock()

    # Proceed with closing shells
    pyzo.localKernelManager.terminateAll() # pylint: disable=no-member
    for shell in pyzo.shells:
        shell._context.close()
        
    t3 = time.clock()

    # Close tools
    for toolname in pyzo.toolManager.getLoadedTools():
        tool = pyzo.toolManager.getTool(toolname)
        tool.close()
        
    t4 = time.clock()

    # Stop all threads (this should really only be daemon threads)
    import threading
    for thread in threading.enumerate():
        if hasattr(thread, 'stop'):
            try:
                thread.stop(0.1)
            except Exception:
                pass
                
    t5 = time.clock()

    if 1: # EKR
        g.pr('\nMainWindowShim.closeEvent 2')
        g.pr('stage 1:          %5.2f' % (t2-t1))
        g.pr('stage 2: shells:  %5.2f' % (t3-t2))
        g.pr('stage 3: tools:   %5.2f' % (t4-t3))
        g.pr('stage 4: threads: %5.2f' % (t5-t4))

    # Proceed as normal
    QtWidgets.QMainWindow.closeEvent(self, event)

    # Harder exit to prevent segfault. Not really a solution,
    # but it does the job until Pyside gets fixed.
    if sys.version_info >= (3,3,0): # and not restarting:
        if hasattr(os, '_exit'):
            os._exit(0)
#@+node:ekr.20190412154124.24: *4* MainWindowShim.monkey_patch_leo
def monkey_patch_leo(self):

    global old_loadFile
    #
    # Use a Leonine pyzo.config.
    if 0:
        # Works, but uses light theme.
        pyzo.config = ConfigShim()
    #
    # Ignore .leo files.
    if 0:
        # Probably will never be needed: it's fine to load .leo files for now.
        from pyzo.core.editorTabs import EditorTabs
        old_loadFile = EditorTabs.loadFile
        g.funcToMethod(loadFile, EditorTabs)
    #
    # Patch pyzo.core.editor.createEditor function
    if 1:
        import pyzo.core.editor
        pyzo.core.editor.createEditor = createEditor
#@+node:ekr.20190412154124.25: *4* MainWindowShim.setMainTitle
def setMainTitle(self, path=None):
    """ Set the title of the main window, by giving a file path.
    """
    self.setWindowTitle('Leo Main Window')
    ### From MainWindow
        # if not path:
            # # Plain title
            # title = "Interactive Editor for Python"
        # else:
            # # Title with a filename
            # name = os.path.basename(path)
            # if os.path.isfile(path):
                # pass
            # elif name == path:
                # path = translate("main", 'unsaved')
            # else:
                # pass  # We hope the given path is informative
            # # Set title
            # tmp = { 'fileName':name, 'filename':name, 'name':name,
                    # 'fullPath':path, 'fullpath':path, 'path':path }
            # title = pyzo.config.advanced.titleText.format(**tmp)
    
        # # Set
        # self.setWindowTitle(title)
#@+node:ekr.20190412154124.26: *4* MainWindowShim.setStyleSheet (override)
firstStyleSheet = True

def setStyleSheet(self, style, *args, **kwargs):
    # print('MainWindowShim.setStyleSheet', style, args, kwargs)
    # A hack: Ignore the first call.
    if self.firstStyleSheet:
        self.firstStyleSheet = False
        return
    QtWidgets.QMainWindow.setStyleSheet(self, style)
#@+node:ekr.20190412154124.27: *3* class MenuShim (object) (To do)
class MenuShim (object):
    '''Adaptor class standing between Leo and Pyzo menus.'''

    def __init__(self):
        self.setObjectName('MenuShim')

    @others
#@+node:ekr.20190412154124.28: *3* class OutlineEditorShim (QFrame)
<< shim classes for OutlineEditorShim >>

from pyzo.codeeditor import CodeEditorBase
assert CodeEditorBase
QAbstractScrollArea = QtWidgets.QAbstractScrollArea

class OutlineEditorShim(QtWidgets.QFrame):
    # QtWidgets.QAbstractScrollArea: placed properly?
    # QtWidgets.QFrame isn't placed properly!
    # Was pyzo.core.editor.PyzoEditor

    somethingChanged = Signal()
    blockCountChanged = Signal()
    breakPointsChanged = Signal()
    
    @others
    
#@+node:ekr.20190412154124.29: *4* << shim classes for OutlineEditorShim >>
class DocumentShim(object):
    modified = False
    def isModified(self):
        return self.modified
    def setModified(self, aBool):
        self.modified = aBool

class ScrollBarShim(object):
    def setValue(self, value):
        pass
    def value(self):
        return 0

class TextCursorShim(object):
    def position(self):
        return 0
    def setPosition(self, pos):
        pass
#@+node:ekr.20190412154124.30: *4* OutlineEditorShim.__init__ (1 shim: parser)
def __init__(self, filename, parent, **kwargs):
    '''
    Called automatically from pyzo's createEditor function.
    '''
    
    assert g.pyzo, g.callers()
    g.pr('\nOutlineEditorShim.__init__', g.shortFileName(filename))
    # g.printObj(g.callers(30).split(','), tag='OutlineEditorShim.__init__')
    super().__init__(parent, **kwargs)
        # CodeEditorBase only passes args to *its* base class.
    self.setObjectName('OutlineEditorShim')
    self.c = None # Set in createOutlineFrame.
    self._filename = self.filename = filename
        # Essential, so the tab will close properly.
    self._name = self.name = os.path.split(filename)[1]
        # To set the tab's name properly.
    if not isinstance(self, CodeEditorBase):
        g.pr('\nOutlineEditorShim: using shims\n')
        #
        # Needed if this is just a QWidget.
        self._breakPoints = {}
        # self.breakPointsChanged.emit(self)
        # self.__breakPointArea.update()
        self.lineEndingsHumanReadable = 'CRLF'
        self.document = DocumentShim
        self.horizontalScrollBar = ScrollBarShim
        self.parser = g.TracingNullObject(tag='OutlineEditorShim.parser')
        self.textCursor = TextCursorShim
        self.verticalScrollBar = ScrollBarShim
    # Create the outline!
    self.createOutlineFrame()
#@+node:ekr.20190412154124.31: *4* OutlineEditorShim.__repr__
def __repr__(self):
    return '<OutlineEditorShim at %s>' % id(self)
#@+node:ekr.20190412154124.32: *4* OutlineEditorShim.createOutlineFrame
def createOutlineFrame(self):
    '''Create the outline frame.'''
    assert g.pyzo, g.callers()
    #
    # Create a new commander.
    # c.frame.finishCreate creates the outline pane.
    #
    g.pr('----- OutlineEditorShim.createOutlineFrame 1')
    self.c = c = g.app.newCommander(
        fileName=self.filename,
        parentFrame=self,
    )
    c.bodyWantsFocus()
#@+node:ekr.20190412154124.33: *4* OutlineEditorShim.finishCreate (WORKS)
def finishCreate(self, c):
    '''Create the entire Leo main window in the shim itself.'''
    import leo.plugins.qt_frame as qt_frame
    import leo.plugins.qt_text as qt_text
    ###
        # import leo.plugins.qt_tree as qt_tree
        # import leo.plugins.qt_menu as qt_menu
        # import leo.core.leoFrame as leoFrame
    self.c = c
    g.pr('\n----- OutlineEditorShim.finishCreate: g.app.log: %r\n' % g.app.log)
        # Called by c.finishCreate.
    f = c.frame
        # A LeoFrame, *not* a QWidget.
    c.frame.c = c
    assert isinstance(c.frame, qt_frame.LeoQtFrame), repr(c.frame)
    self.setStyleSheet('background: red')
        # No longer seen, which is good.
    #
    # From SDIFrameFactory.createFrame
    dw = qt_frame.DynamicWindow(c, parent=self)
    dw.construct()
        # This just creates frames for components.
    dw.show()
    f.top = dw
    assert isinstance(dw.treeWidget, qt_frame.LeoQTreeWidget), repr(dw.treeWidget)
    #
    # From LeoQtFrame.finishCreate.
    f.createIconBar() # A base class method.
    f.createSplitterComponents()
    ### f.createStatusLine() # A base class method.
    f.createFirstTreeNode() # Call the base-class method.
        # Does some basic inits.
    if 1:
        lm = g.app.loadManager
        fn = c.fileName()
        theFile = lm.openLeoOrZipFile(fn)
        # Enable the log.
        g.app.unlockLog()
        c.frame.log.enable(True)
        # Phase 2: Create the outline.
        ### g.doHook("open1", old_c=None, c=c, new_c=c, fileName=fn)
        if theFile:
            ### readAtFileNodesFlag = bool(previousSettings)
            readAtFileNodesFlag = True
            # The log is not set properly here.
            assert c.p
            ok = lm.readOpenedLeoFile(c, fn, readAtFileNodesFlag, theFile)
                # Call c.fileCommands.openLeoFile to read the .leo file.
            if not ok:
                g.trace('lm.readOpenedLeoFile FAILED', fn)
    ### f.menu = qt_menu.LeoQtMenu(c, f, label='top-level-menu')
    f.menu=g.NullObject(tag='c.frame.menu')
    g.app.windowList.append(f)
    f.miniBufferWidget = qt_text.QMinibufferWrapper(c)
    c.setLog()
    c.redraw()
    c.bodyWantsFocus()
#@+node:ekr.20190412154124.34: *4* OutlineEditorShim.set_style
def setStyle(self, style):
    
    suppress = (
        'Editor.Highlight current line',
        'Editor.Indentation guides',
        'Editor.Line numbers',
        'Editor.Long line indicator',
    )
    
    def use_style(key):
        return key not in suppress and not key.startswith('Syntax')

    super().setStyle({
        z: style.get(z) for z in style.keys() if use_style(z)
    })
#@+node:ekr.20190412154124.35: *4* OutlineEditorShim:do-nothings
def blockCount(self):
    return 0

def breakPoints(self):
    return list(sorted(self._breakPoints))

def id(self):
    return self._filename or self._name

def indentUsingSpaces(self):
    return True

def indentWidth(self):
    return 4

def save(self, filename=None):
    pass

def setIndentUsingSpaces(self, style):
    pass
    
def setChanged(self, *args, **kwargs):
    pass
    # sys.__stdout__.write('OutlineEditorShim: %r, %r' % (args, kwargs))

def setCheckedOption(self, val):
    pass

def setDebugLineIndicator(self, val):
    pass

def setIndentWidth(self, width):
    pass

def setParser(self, val):
    pass

def setPlainText(self, text):
    pass

def setTextCursor(self, obj):
    pass

def setTitleInMainWindow(self):
    pass
#@+node:ekr.20190412154124.36: *3* class ToolShim (Needed???)
class ToolShim (object):
    '''
    A Shim to make a QWidget into a Pyzo Tool window.
    
    From the docstring in pyzo/tools.__init__.py:
    
    A tool consists of a module which contains a class:
    - The id of a tool is its lower-cased module name.
    - The module should contain a class corresponding to its id.
    
    The module may contain the following extra variables,
    which should be placed within the first 50 lines of code:
    
    - tool_name:      A readable name for the tool. May contain spaces.
    - tool_summary:   A single line short summary of the tool.
                      Will be displayed in the statusbar.
    '''
#@-all
#@@nosearch
#@-leo
