.. @+leo-ver=5-thin
.. @+node:ekr.20100119205347.6015: * @file ../doc/leoToDo.txt
.. @@language rest # override the default .txt coloring.

.. @+all
.. @+node:ekr.20110921094450.6956: ** To do: 4.10.1
@language rest
.. @+node:ekr.20111017132257.15891: *3* Urgent: Get IPython working again
.. @+node:ekr.20111017132257.15890: *4* Investigate how IPython hijacks event loops
http://groups.google.com/group/leo-editor/browse_thread/thread/e1dc6439bf8b17f9
.. @+node:ekr.20111009162434.7204: *4* Port ipy_leo.py to latest version of IPython
.. @+node:ekr.20120208062900.10146: *3* Urgent: Complete new_modes
@nocolor-node

This will eliminate the hacks for *entry-commands* and *command-prompt keys.

ModeController: a wrapper for g.app.config.modeCommandsDict
.. @+node:ekr.20120212060348.10374: *4*  << global switches >>
trace_startup = False
    # These traces use print instead of g.trace so that
    # the traces can add class info the method name.

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

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

# Debugging options.
restore_selection_range = False

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

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

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

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

enableDB = True
    # Don't even think about eliminating this constant:
    # it is needed for debugging.
    
# These print statements have been moved to writeWaitingLog.
# This allows for better --silent operation.
if 0:
    print('*** isPython3: %s' % isPython3)
    if not enableDB:
        print('** leoGlobals.py: caching disabled')
.. @+node:ekr.20120208061308.10131: *4* Weird stroke code
@nocolor-node

isFKey and k.isPlainKey don't seem to know the types of their args.
.. @+node:ekr.20061031131434.181: *5* k.Shortcuts & bindings
.. @+node:ekr.20061031131434.176: *6* k.computeInverseBindingDict
def computeInverseBindingDict (self):

    k = self ; d = {}

    # keys are minibuffer command names, values are shortcuts.
    for stroke in k.bindingsDict.keys():
        assert g.isStroke(stroke),repr(stroke)
        aList = k.bindingsDict.get(stroke,[])
        for si in aList:
            assert g.isShortcutInfo(si),si
            shortcutList = d.get(si.commandName,[])
            
            # The shortcutList consists of tuples (pane,stroke).
            # k.inverseBindingDict has values consisting of these tuples.
            aList = k.bindingsDict.get(stroke,g.ShortcutInfo(kind='dummy',pane='all'))
                    # Important: only si.pane is required below.
            for si in aList:
                assert g.isShortcutInfo(si),si
                pane = '%s:' % (si.pane)
                data = (pane,stroke)
                if data not in shortcutList:
                    shortcutList.append(data)

            d [si.commandName] = shortcutList

    return d
.. @+node:ekr.20061031131434.179: *6* k.getShortcutForCommand/Name
def getShortcutForCommandName (self,commandName):

    k = self ; c = k.c
    command = c.commandsDict.get(commandName)
    if command:
        for stroke in k.bindingsDict:
            assert g.isStroke(stroke),repr(stroke)
            aList = k.bindingsDict.get(stroke,[])
            for si in aList:
                assert g.isShortcutInfo(si),si
                if si.commandName == commandName:
                    return stroke
    return None

def getShortcutForCommand (self,command):

    k = self ; c = k.c
    if command:
        for stroke in k.bindingsDict:
            assert g.isStroke(stroke),repr(stroke)
            aList = k.bindingsDict.get(stroke,[])
            for si in aList:
                assert g.isShortcutInfo(si),si
                if si.commandName == command.__name__:
                    return stroke
    return None
.. @+node:ekr.20090518072506.8494: *6* k.isFKey
def isFKey (self,stroke):

    k = self
    if not stroke: return False
    assert g.isString(stroke) or g.isStroke(stroke)
    s = stroke.s if g.isStroke(stroke) else stroke
    s = s.lower()
    return s.startswith('f') and len(s) <= 3 and s[1:].isdigit()
.. @+node:ekr.20061031131434.182: *6* k.isPlainKey
def isPlainKey (self,stroke):

    '''Return true if the shortcut refers to a plain (non-Alt,non-Ctl) key.'''

    k = self
    if not stroke: return False

    assert g.isString(stroke) or g.isStroke(stroke)
    shortcut = stroke.s if g.isStroke(stroke) else stroke

    # altgr combos (Alt+Ctrl) are always plain keys
    if shortcut.startswith('Alt+Ctrl+') and not self.enable_alt_ctrl_bindings:
        return True

    for z in ('Alt','Ctrl','Command','Meta'):
        if shortcut.find(z) != -1:            
            return False
    else:
        # Careful, allow bare angle brackets for unit tests.
        if shortcut.startswith('<') and shortcut.endswith('>'):
            shortcut = shortcut[1:-1]

        isPlain = (
            len(shortcut) == 1 or
            len(k.guiBindNamesInverseDict.get(shortcut,'')) == 1 or
            # A hack: allow Return to be bound to command.
            shortcut in ('Tab','\t')
        )

        # g.trace(isPlain,repr(shortcut))
        return isPlain and not self.isFKey(shortcut)
.. @+node:ekr.20061031131434.191: *6* k.prettyPrintKey
def prettyPrintKey (self,stroke,brief=False):

    trace = False and not g.unitTesting
    k = self
    if not stroke:
        s = ''
    elif g.isStroke(stroke):
        s = stroke.s
    else:
        s = stroke

    if not s: return ''

    shift = s.find("shift") >= 0 or s.find("shft") >= 0

    # Replace all minus signs by plus signs, except a trailing minus:
    if s.endswith('-'): s = s[:-1].replace('-','+') + '-'
    else:               s = s.replace('-','+')
    fields = s.split('+')
    last = fields and fields[-1]
    if trace: g.trace('fields',fields)
    if last and len(last) == 1:
        prev = s[:-1]
        if last.isalpha():
            if last.isupper():
                if not shift:
                    s = prev + 'Shift+' + last
            elif last.islower():
                if not prev:
                    s = last.upper()
                else:
                    s = prev + last.upper()
    else:
        last = k.guiBindNamesInverseDict.get(last,last)
        if fields and fields[:-1]:
            s = '%s+%s' % ('+'.join(fields[:-1]),last)
        else:
            s = last
    if s.endswith(' '):
        s = s[:-1]+'Space' # 2010/11/06
        
    return s
.. @+node:ekr.20061031131434.184: *6* k.strokeFromSetting
def strokeFromSetting (self,setting,addKey=True):

    k = self

    trace = False and not g.unitTesting # and setting.lower().find('ctrl-x') > -1
    verbose = False
    if not setting:
        return None
    
    assert g.isString(setting)

    s = g.stripBrackets(setting.strip())
    << define cmd, ctrl, alt, shift >>
    if k.swap_mac_keys and sys.platform == "darwin":
        << swap cmd and ctrl keys >>
    << convert minus signs to plus signs >>
    << compute the last field >>
    << compute shortcut >>

    if trace and verbose:
        g.trace('%20s %s' % (setting,shortcut),g.callers())
    
    return g.KeyStroke(shortcut) if shortcut else None

canonicalizeShortcut = strokeFromSetting # For compatibility.
### strokeFromSetting = shortcutFromSetting
.. @+node:ekr.20061031131434.185: *7* << define cmd, ctrl, alt, shift >>
s2 = s.lower()

cmd   = s2.find("cmd") >= 0     or s2.find("command") >= 0
ctrl  = s2.find("control") >= 0 or s2.find("ctrl") >= 0
alt   = s2.find("alt") >= 0
shift = s2.find("shift") >= 0   or s2.find("shft") >= 0
meta  = s2.find("meta") >= 0
.. @+node:ekr.20061031131434.186: *7* << swap cmd and ctrl keys >>
if ctrl and not cmd:
    cmd = True ; ctrl = False
if alt and not ctrl:
    ctrl = True ; alt = False
.. @+node:ekr.20061031131434.187: *7* << convert minus signs to plus signs >>
# Replace all minus signs by plus signs, except a trailing minus:
if s.endswith('-'):
    s = s[:-1].replace('-','+') + '-'
else:
    s = s.replace('-','+')
.. @+node:ekr.20061031131434.188: *7* << compute the last field >>
if s.endswith('+'):
    last = '+'
else:
    fields = s.split('+') # Don't lower this field.
    last = fields and fields[-1]
    if not last:
        if not g.app.menuWarningsGiven:
            g.pr("bad shortcut specifier:", repr(s),repr(setting))
            g.trace(g.callers())
        return None

if len(last) == 1:
    last2 = k.guiBindNamesDict.get(last) # Fix new bug introduced in 4.4b2.
    if last2:
        last = last2
    else:
        if last.isalpha():
            if shift:
                last = last.upper()
                shift = False # It is Ctrl-A, not Ctrl-Shift-A.
            else:
                last = last.lower()
        # New in Leo 4.4.2: Alt-2 is not a key event!
        if addKey and last.isdigit():
            last = 'Key-' + last
else:
    # Translate from a made-up (or lowercase) name to 'official' Tk binding name.
    # This is a *one-way* translation, done only here.
    d = k.settingsNameDict
    last = d.get(last.lower(),last)
.. @+node:ekr.20061031131434.189: *7* << compute shortcut >>
table = (
    (alt, 'Alt+'),
    (ctrl,'Ctrl+'),
    (cmd, 'Command+'),
    (meta,'Meta+'),
    (shift,'Shift+'),
    (True, last),
)

# new in 4.4b3: convert all characters to unicode first.
shortcut = ''.join([g.toUnicode(val) for flag,val in table if flag])
.. @+node:ekr.20110606004638.16929: *6* k.stroke2char
def stroke2char (self,stroke):
    
    '''Convert a stroke to an (insertable) char.
    
    This method allows Leo to use strokes everywhere.'''
    
    trace = False and not g.unitTesting
    k = self
    
    if not stroke: return ''
    s = stroke.s
    
    # Allow bare angle brackets for unit tests.
    if s.startswith('<') and s.endswith('>'):
        s = s[1:-1]
        
    if len(s) == 0: return ''
    if len(s) == 1: return s
        
    for z in ('Alt','Ctrl','Command','Meta'):
        if s.find(z) != -1:            
            return ''
            # This is not accurate: leoQtEventFilter retains
            # the spelling of Alt-Ctrl keys because of the
            # @bool enable_alt_ctrl_bindings setting.
            
    # Special case the gang of four, plus 'Escape',
    d = {
        'BackSpace':'\b',
        'Escape':'Escape',
        'Linefeed':'\r',
        'Return':'\n',
        'Tab':'\t',
    }
    ch = d.get(s)
    if ch: return ch
            
    # First, do the common translations.
    ch = k.guiBindNamesInverseDict.get(s)
    if ch:
        if trace: g.trace(repr(stroke),repr(ch))
        return ch
    
    # A much-simplified form of code in k.strokeFromSetting.
    shift = s.find('Shift+') > -1 or s.find('Shift-') > -1
    s = s.replace('Shift+','').replace('Shift-','')
    
    last = s #  Everything should have been stripped.
    
    if len(s) == 1 and s.isalpha():
        if shift:
            s = last.upper()
        else:
            s = last.lower()
    
    val = g.choose(len(s)==1,s,'')

    if trace: g.trace(repr(stroke),repr(val)) # 'shift',shift,
    return val
.. @+node:ekr.20061031131434.100: *4* k.addModeCommands (enterModeCallback)
def addModeCommands (self):

    '''Add commands created by @mode settings to c.commandsDict and k.inverseCommandsDict.'''
    
    trace = False and not g.unitTesting
    
    if trace: g.trace('(k)')

    k = self ; c = k.c
    d = g.app.config.modeCommandsDict # Keys are command names: enter-x-mode.

    # Create the callback functions and update c.commandsDict and k.inverseCommandsDict.
    for key in d.keys():

        def enterModeCallback (event=None,name=key):
            k.enterNamedMode(event,name)

        c.commandsDict[key] = f = enterModeCallback
        k.inverseCommandsDict [f.__name__] = key
        if trace: g.trace(f.__name__,key,'len(c.commandsDict.keys())',len(list(c.commandsDict.keys())))
.. @+node:ekr.20120208064440.10192: *4* From leoConfig...
.. @+node:ekr.20060102103625.1: *5* doMode (ParserBaseClass)
def doMode(self,p,kind,name,val):

    '''Parse an @mode node and create the enter-<name>-mode command.'''

    trace = False and not g.unitTesting
    c,k = self.c,self.c.k
    
    if g.new_modes:
        aList = []
        for line in g.splitLines(p.b):
            line = line.strip()
            if line and not g.match(line,0,'#'):
                name2,si = self.parseShortcutLine('*mode-setting*',line)
                aList.append((name2,si),)
        k.modeController.makeMode(name,aList)
    else:
        name1 = name

        # g.trace('%20s' % (name),c.fileName())
        modeName = self.computeModeName(name)
    
        d = g.TypedDictOfLists(
            name='modeDict for %s' % (modeName),
            keyType=type('commandName'),valType=g.ShortcutInfo)
    
        s = p.b
        lines = g.splitLines(s)
        for line in lines:
            line = line.strip()
            if line and not g.match(line,0,'#'):
                name,si = self.parseShortcutLine('*mode-setting*',line)
                assert g.isShortcutInfo(si),si
                if not name:
                    # An entry command: put it in the special *entry-commands* key.
                    d.add('*entry-commands*',si)
                elif si is not None:
                    # A regular shortcut.
                    si.pane = modeName
                    aList = d.get(name,[])
                    for z in aList:
                        assert g.isShortcutInfo(z),z
                    # Important: use previous bindings if possible.
                    key2,aList2 = c.config.getShortcut(name)
                    for z in aList2:
                        assert g.isShortcutInfo(z),z
                    aList3 = [z for z in aList2 if z.pane != modeName]
                    if aList3:
                        # g.trace('inheriting',[b.val for b in aList3])
                        aList.extend(aList3)
                    aList.append(si)
                    d.replace(name,aList)
                    
                    if 0: #### Why would we want to do this????
                        #### Old code: we have to save/restore self.shortcutsDict.
                            #### self.set(p,"shortcut",name,aList)
                        # Set the entry directly.
                        d2 = self.shortcutsDict
                        gs = d2.get(key2)
                        if gs:
                            assert g.isGeneralSetting(gs)
                            path = gs.path
                            if c.os_path_finalize(c.mFileName) != c.os_path_finalize(path):
                                g.es("over-riding setting:",name,"from",path)
        
                        # Important: we can't use c here: it may be destroyed!
                        d2 [key2] = g.GeneralSetting(
                            kind,path=c.mFileName,val=val,tag='setting')
    
            # Restore the global shortcutsDict.
            ##### self.shortcutsDict = old_d
            
            if trace: g.trace(d.dump())
        
            # Create the command, but not any bindings to it.
            self.createModeCommand(modeName,name1,d)
.. @+node:ekr.20041120112043: *5* parseShortcutLine (ParserBaseClass)
def parseShortcutLine (self,kind,s):

    '''Parse a shortcut line.  Valid forms:

    --> entry-command
    settingName = shortcut
    settingName ! paneName = shortcut
    command-name --> mode-name = binding
    command-name --> same = binding
    '''

    trace = False and not g.unitTesting and kind == '*mode-setting*'
    c,k = self.c,self.c.k
    assert c
    name = val = nextMode = None ; nextMode = 'none'
    i = g.skip_ws(s,0)

    if g.match(s,i,'-->'): # New in 4.4.1 b1: allow mode-entry commands.
        j = g.skip_ws(s,i+3)
        i = g.skip_id(s,j,'-')
        entryCommandName = s[j:i]
        if trace: g.trace('-->',entryCommandName)
        return None,g.ShortcutInfo('*entry-command*',commandName=entryCommandName)

    j = i
    i = g.skip_id(s,j,'-') # New in 4.4: allow Emacs-style shortcut names.
    name = s[j:i]
    if not name:
        if trace: g.trace('no name',repr(s))
        return None,None

    # New in Leo 4.4b2.
    i = g.skip_ws(s,i)
    if g.match(s,i,'->'): # New in 4.4: allow pane-specific shortcuts.
        j = g.skip_ws(s,i+2)
        i = g.skip_id(s,j)
        nextMode = s[j:i]

    i = g.skip_ws(s,i)
    if g.match(s,i,'!'): # New in 4.4: allow pane-specific shortcuts.
        j = g.skip_ws(s,i+1)
        i = g.skip_id(s,j)
        pane = s[j:i]
        if not pane.strip(): pane = 'all'
    else: pane = 'all'

    i = g.skip_ws(s,i)
    if g.match(s,i,'='):
        i = g.skip_ws(s,i+1)
        val = s[i:]

    # New in 4.4: Allow comments after the shortcut.
    # Comments must be preceded by whitespace.
    comment = ''
    if val:
        i = val.find('#')
        if i > 0 and val[i-1] in (' ','\t'):
            # comment = val[i:].strip()
            val = val[:i].strip()

    stroke = k.strokeFromSetting(val)
    assert g.isStrokeOrNone(stroke),stroke
    # g.trace('stroke',stroke)
    si = g.ShortcutInfo(kind=kind,nextMode=nextMode,pane=pane,stroke=stroke)
    if trace: g.trace('%25s %s' % (name,si))
    return name,si
.. @+node:ekr.20060102103625: *5* createModeCommand (ParserBaseClass)
def createModeCommand (self,modeName,name,modeDict):

    modeName = 'enter-' + modeName.replace(' ','-')

    i = name.find('::')
    if i > -1:
        # The prompt is everything after the '::'
        prompt = name[i+2:].strip()
        modeDict ['*command-prompt*'] = g.ShortcutInfo(kind=prompt)

    # Save the info for k.finishCreate and k.makeAllBindings.
    d = g.app.config.modeCommandsDict

    # New in 4.4.1 b2: silently allow redefinitions of modes.
    d [modeName] = modeDict
    
.. @+node:ekr.20120208064440.10148: *4* class ModeController
class ModeController:
    
    def __init__ (self,c):
        self.c = c
        self.d = {} # Keys are command names, values are modes.
        self.k = c.k
        g.trace(self)
            
    def __repr__(self):
        return '<ModeController %s>' % self.c.shortFileName()
        
    __str__ = __repr__
            
    @others
.. @+node:ekr.20120208064440.10161: *5* addModeCommands (ModeController)
def addModeCommands(self):
    
    g.trace(self,self.d)
    
    for mode in self.d.values():
        mode.createModeCommand()
.. @+node:ekr.20120208064440.10163: *5* getMode (ModeController)
def getMode (self,modeName):
    
    g.trace(self)
    
    mode = self.d.get(modeName)
    g.trace(modeName,mode)
    return mode
    
.. @+node:ekr.20120208064440.10164: *5* makeMode (ModeController)
def makeMode (self,name,aList):


    mode = ModeInfo(self.c,name,aList)
    
    g.trace(self,mode.name,mode)
    self.d[mode.name] = mode
    
.. @+node:ekr.20120208064440.10150: *4* class ModeInfo
class ModeInfo:
    
    def __repr__(self):
        return '<ModeInfo %s>' % self.name
        
    __str__ = __repr__
    
    @others
    
.. @+node:ekr.20120208064440.10193: *5*  ctor (ModeInfo)
def __init__ (self,c,name,aList):
    
    g.trace(name,aList)
    
    self.c = c
    self.d = {} # The bindings in effect for this mode.
        # Keys are names of (valid) command names, values are ShortcutInfo objects.
    self.entryCommands = []
        # A list of ShortcutInfo objects.
    self.k = c.k
    self.name = self.computeModeName(name)
    self.prompt = self.computeModePrompt(self.name)

    self.init(name,aList)
.. @+node:ekr.20120208064440.10152: *5* computeModeName (ModeInfo)
def computeModeName (self,name):

    s = name.strip().lower()
    j = s.find(' ')
    if j > -1: s = s[:j]
    if s.endswith('mode'):
        s = s[:-4].strip()
    if s.endswith('-'):
        s = s[:-1]

    i = s.find('::')
    if i > -1:
        # The actual mode name is everything up to the "::"
        # The prompt is everything after the prompt.
        s = s[:i]

    return s + '-mode'
.. @+node:ekr.20120208064440.10156: *5* computeModePrompt (ModeInfo)
def computeModePrompt (self,name):
    
    assert name == self.name
    s = 'enter-' + name.replace(' ','-')
    i = s.find('::')
    if i > -1:
        # The prompt is everything after the '::'
        prompt = s[i+2:].strip()
    else:
        prompt = s
    
    return prompt
.. @+node:ekr.20120208064440.10160: *5* createModeBindings (ModeInfo) (NOT USED)
##### k.createModeBindings is used instead????

def createModeBindings (self,w):

    '''Create mode bindings for w, a text widget.'''

    trace = False and not g.unitTesting
    c,d,k,modeName = self.c,self.d,self.k,self.name
    for commandName in d.keys():
        func = c.commandsDict.get(commandName)
        if not func:
            g.es_print('no such command: %s Referenced from %s' % (
                commandName,modeName))
            continue
        aList = d.get(commandName,[])
        for si in aList:
            assert g.isShortcutInfo(si),si
            if trace: g.trace(si)
            stroke = si.stroke
            # Important: si.val is canonicalized.
            if stroke and stroke not in ('None','none',None):
                if trace:
                    g.trace(
                        g.app.gui.widget_name(w), modeName,
                        '%10s' % (stroke),
                        '%20s' % (commandName),
                        si.nextMode)
                        
                assert g.isStroke(stroke)
                k.makeMasterGuiBinding(stroke)

                # Create the entry for the mode in k.masterBindingsDict.
                # Important: this is similar, but not the same as k.bindKeyToDict.
                # Thus, we should **not** call k.bindKey here!
                d2 = k.masterBindingsDict.get(modeName,{})
                d2 [stroke] = g.ShortcutInfo(
                    kind = 'mode<%s>' % (modeName), # 2012/01/23
                    commandName=commandName,
                    func=func,
                    nextMode=si.nextMode,
                    stroke=stroke)
                k.masterBindingsDict[modeName] = d2
                if trace: g.trace(modeName,d2)
.. @+node:ekr.20120208064440.10195: *5* createModeCommand (ModeInfo)
def createModeCommand (self):
    
    g.trace(self)

    c,k = self.c,self.k
    key = 'enter-' + self.name.replace(' ','-')
   
    def enterModeCallback (event=None,self=self):
        self.enterMode()

    c.commandsDict[key] = f = enterModeCallback
    k.inverseCommandsDict [f.__name__] = key
    
    g.trace('(ModeInfo)',f.__name__,key,'len(c.commandsDict.keys())',len(list(c.commandsDict.keys())))
.. @+node:ekr.20120208064440.10180: *5* enterMode (ModeInfo)
def enterMode (self):
    
    g.trace('(ModeInfo)')

    c,k = self.c,self.k
    c.inCommand = False
        # Allow inner commands in the mode.
    event=None ####
    k.generalModeHandler(event,modeName=self.name)
.. @+node:ekr.20120208064440.10153: *5* init (ModeInfo) (Can we check command names here??)
def init (self,name,dataList):
    
    '''aList is a list of tuples (commandName,si).'''
    
    trace = False and not g.unitTesting
    c,d,k,modeName = self.c,self.d,self.c.k,self.name
    for name,si in dataList:
    
        assert g.isShortcutInfo(si),si
        if not name:
            if trace: g.trace('entry command',si)
            #### An entry command: put it in the special *entry-commands* key.
            #### d.add('*entry-commands*',si)
            self.entryCommands.append(si)
        elif si is not None:
            # A regular shortcut.
            si.pane = modeName
            aList = d.get(name,[])
            for z in aList:
                assert g.isShortcutInfo(z),z
            # Important: use previous bindings if possible.
            key2,aList2 = c.config.getShortcut(name)
            for z in aList2:
                assert g.isShortcutInfo(z),z
            aList3 = [z for z in aList2 if z.pane != modeName]
            if aList3:
                if trace: g.trace('inheriting',[si.val for si in aList3])
                aList.extend(aList3)
            aList.append(si)
            d[name] = aList
.. @+node:ekr.20120208064440.10158: *5* initMode (ModeInfo)
def initMode (self):
    
    trace = False and not g.unitTesting
    c,k = self.c,self.c.k
    
    ####
    # d = g.app.config.modeCommandsDict.get('enter-'+modeName)
    # if not d:
        # self.badMode(modeName)
        # return
    # else:
        # k.modeBindingsDict = d
        # si = d.get('*command-prompt*')
        # if si:
            # prompt = si.kind # A kludge.
        # else:
            # prompt = modeName
        # if trace: g.trace('modeName',modeName,prompt,'d.keys()',list(d.keys()))

    k.inputModeName = self.name
    k.silentMode = False

    #### aList = d.get('*entry-commands*',[])
    for si in self.entryCommands:
        assert g.isShortcutInfo(si),si
        commandName = si.commandName
        if trace: g.trace('entry command:',commandName)
        k.simulateCommand(commandName)
        # Careful, the command can kill the commander.
        if g.app.quitting or not c.exists: return
        # New in Leo 4.5: a startup command can immediately transfer to another mode.
        if commandName.startswith('enter-'):
            if trace: g.trace('redirect to mode',commandName)
            return

    # Create bindings after we know whether we are in silent mode.
    # w = g.choose(k.silentMode,k.modeWidget,k.w)
    w = k.modeWidget if k.silentMode else k.w
    k.createModeBindings(self.name,self.d,w)
    #### self.createModeBindings(w)
    k.showStateAndMode(prompt=self.name)
.. @+node:ekr.20120208064440.10191: *4* From leoKeys...
.. @+node:ekr.20061031131434.76: *5* k.__init__
def __init__ (self,c):

    '''Create a key handler for c.'''
    
    trace = (False or g.trace_startup) and not g.unitTesting
    if trace: print('k.__init__')

    self.c = c
    self.dispatchEvent = None
    self.inited = False         # Set at end of finishCreate.
    self.swap_mac_keys = False  #### How to init this ????
    self.w = None
            # Note: will be None for nullGui.

    # Generalize...
    self.x_hasNumeric = ['sort-lines','sort-fields']

    self.altX_prompt = 'full-command: '
    
    # Access to data types defined in leoKeys.py
    self.KeyStroke = g.KeyStroke
    
    # Define all ivars...
    self.defineExternallyVisibleIvars()
    self.defineInternalIvars()
    self.defineSettingsIvars()
    
    if g.new_modes:
        self.modeController = ModeController(c)

    self.defineTkNames()
    self.defineSpecialKeys()
    self.defineSingleLineCommands()
    self.defineMultiLineCommands()
    self.autoCompleter = AutoCompleterClass(self)
    self.qcompleter = None # Set by AutoCompleter.start.

    self.setDefaultUnboundKeyAction()
    self.setDefaultEditingAction() # 2011/02/09
.. @+node:ekr.20120208064440.10190: *5* k.Modes (no change)
.. @+node:ekr.20061031131434.100: *6* k.addModeCommands (enterModeCallback)
def addModeCommands (self):

    '''Add commands created by @mode settings to c.commandsDict and k.inverseCommandsDict.'''
    
    trace = False and not g.unitTesting
    
    if trace: g.trace('(k)')

    k = self ; c = k.c
    d = g.app.config.modeCommandsDict # Keys are command names: enter-x-mode.

    # Create the callback functions and update c.commandsDict and k.inverseCommandsDict.
    for key in d.keys():

        def enterModeCallback (event=None,name=key):
            k.enterNamedMode(event,name)

        c.commandsDict[key] = f = enterModeCallback
        k.inverseCommandsDict [f.__name__] = key
        if trace: g.trace(f.__name__,key,'len(c.commandsDict.keys())',len(list(c.commandsDict.keys())))
.. @+node:ekr.20061031131434.157: *6* k.badMode
def badMode(self,modeName):

    k = self

    k.clearState()
    if modeName.endswith('-mode'): modeName = modeName[:-5]
    k.setLabelGrey('@mode %s is not defined (or is empty)' % modeName)
.. @+node:ekr.20061031131434.158: *6* k.createModeBindings
def createModeBindings (self,modeName,d,w):

    '''Create mode bindings for the named mode using dictionary d for w, a text widget.'''

    trace = False and not g.unitTesting
    k = self ; c = k.c
    assert d.name().endswith('-mode')
    for commandName in d.keys():
        if commandName in ('*entry-commands*','*command-prompt*'):
            # These are special-purpose dictionary entries.
            continue
        func = c.commandsDict.get(commandName)
        if not func:
            g.es_print('no such command:',commandName,'Referenced from',modeName)
            continue
        aList = d.get(commandName,[])
        for si in aList:
            assert g.isShortcutInfo(si),si
            stroke = si.stroke
            # Important: si.val is canonicalized.
            if stroke and stroke not in ('None','none',None):
                if trace:
                    g.trace(
                        g.app.gui.widget_name(w), modeName,
                        '%10s' % (stroke),
                        '%20s' % (commandName),
                        si.nextMode)
                        
                assert g.isStroke(stroke)

                k.makeMasterGuiBinding(stroke)

                # Create the entry for the mode in k.masterBindingsDict.
                # Important: this is similar, but not the same as k.bindKeyToDict.
                # Thus, we should **not** call k.bindKey here!
                d2 = k.masterBindingsDict.get(modeName,{})
                d2 [stroke] = g.ShortcutInfo(
                    kind = 'mode<%s>' % (modeName), # 2012/01/23
                    commandName=commandName,
                    func=func,
                    nextMode=si.nextMode,
                    stroke=stroke)
                k.masterBindingsDict [ modeName ] = d2
.. @+node:ekr.20120208064440.10179: *6* k.endMode
def endMode(self):

    k = self ; c = k.c

    w = g.app.gui.get_focus(c)
    if w:
        c.frame.log.deleteTab('Mode') # Changes focus to the body pane

    k.endCommand(k.stroke)
    k.inputModeName = None
    k.clearState()
    k.resetLabel()
    k.showStateAndMode() # Restores focus.

    if w:
        c.widgetWantsFocusNow(w)
.. @+node:ekr.20061031131434.160: *6* k.enterNamedMode
def enterNamedMode (self,event,commandName):

    k = self ; c = k.c
    modeName = commandName[6:]
    c.inCommand = False # Allow inner commands in the mode.
    k.generalModeHandler(event,modeName=modeName)
.. @+node:ekr.20061031131434.161: *6* k.exitNamedMode
def exitNamedMode (self,event=None):
    
    '''Exit an input mode.'''

    k = self

    if k.inState():
        k.endMode()

    k.showStateAndMode()
.. @+node:ekr.20061031131434.165: *6* k.modeHelp & helper (revise helper)
def modeHelp (self,event):

    '''The mode-help command.

    A possible convention would be to bind <Tab> to this command in most modes,
    by analogy with tab completion.'''

    k = self ; c = k.c

    c.endEditing()

    # g.trace(k.inputModeName)

    if k.inputModeName:
        d = g.app.config.modeCommandsDict.get('enter-'+k.inputModeName)
        k.modeHelpHelper(d)

    if not k.silentMode:
        c.minibufferWantsFocus()

    return
.. @+node:ekr.20061031131434.166: *7* modeHelpHelper
def modeHelpHelper (self,d):

    k = self ; c = k.c ; tabName = 'Mode'
    c.frame.log.clearTab(tabName)
    data,n = [],0
    for key in sorted(d.keys()):
        if key in ('*entry-commands*','*command-prompt*'):
            pass
        else:
            aList = d.get(key)
            for si in aList:
                assert g.isShortcutInfo(si),si
                stroke = si.stroke
                if stroke not in (None,'None'):
                    s1 = key
                    s2 = k.prettyPrintKey(stroke)
                    n = max(n,len(s1))
                    data.append((s1,s2),)

    data.sort()
    modeName = k.inputModeName.replace('-',' ')
    if modeName.endswith('mode'):
        modeName = modeName[:-4].strip()
    g.es('','%s mode\n\n' % modeName,tabName=tabName)

    # This isn't perfect in variable-width fonts.
    for s1,s2 in data:
        g.es('','%*s %s' % (n,s1,s2),tabName=tabName)
.. @+node:ekr.20061031131434.164: *6* k.reinitMode (call k.createModeBindings???)
def reinitMode (self,modeName):

    k = self ; c = k.c

    d = k.modeBindingsDict

    k.inputModeName = modeName
    w = g.choose(k.silentMode,k.modeWidget,k.w)
    k.createModeBindings(modeName,d,w)

    if k.silentMode:
        k.showStateAndMode()
    else:
        # Do not set the status line here.
        k.setLabelBlue(modeName+': ',protect=True)

.. @+node:ekr.20120208064440.10199: *6* k.generalModeHandler (OLD)
def generalModeHandler (self,event,
    commandName=None,func=None,modeName=None,nextMode=None,prompt=None):

    '''Handle a mode defined by an @mode node in leoSettings.leo.'''

    k = self ; c = k.c
    state = k.getState(modeName)
    trace = (False or g.trace_modes) and not g.unitTesting

    if trace: g.trace(modeName,'state',state)

    if state == 0:
        k.inputModeName = modeName
        k.modePrompt = prompt or modeName
        k.modeWidget = event and event.widget
        k.setState(modeName,1,handler=k.generalModeHandler)
        self.initMode(event,modeName)
        # Careful: k.initMode can execute commands that will destroy a commander.
        if g.app.quitting or not c.exists: return # (for Tk) 'break'
        if not k.silentMode:
            if c.config.getBool('showHelpWhenEnteringModes'):
                k.modeHelp(event)
            else:
                c.frame.log.hideTab('Mode')
    elif not func:
        g.trace('No func: improper key binding')
    else:
        if commandName == 'mode-help':
            func(event)
        else:
            savedModeName = k.inputModeName # Remember this: it may be cleared.
            self.endMode()
            if trace or c.config.getBool('trace_doCommand'): g.trace(func.__name__)
            # New in 4.4.1 b1: pass an event describing the original widget.
            if event:
                event.w = event.widget = k.modeWidget
            else:
                event = g.app.gui.create_key_event(c,None,None,k.modeWidget)
            if trace: g.trace(modeName,'state',state,commandName,'nextMode',nextMode)
            func(event)
            if g.app.quitting or not c.exists:
                return # (for Tk) 'break'
            if nextMode in (None,'none'):
                # Do *not* clear k.inputModeName or the focus here.
                # func may have put us in *another* mode.
                pass
            elif nextMode == 'same':
                silent = k.silentMode
                k.setState(modeName,1,handler=k.generalModeHandler)
                self.reinitMode(modeName) # Re-enter this mode.
                k.silentMode = silent
            else:
                k.silentMode = False # All silent modes must do --> set-silent-mode.
                self.initMode(event,nextMode) # Enter another mode.
                # Careful: k.initMode can execute commands that will destroy a commander.
                if g.app.quitting or not c.exists: return # (for Tk) 'break'
.. @+node:ekr.20061031131434.156: *5* k.Modes (changed)
.. @+node:ekr.20061031131434.163: *6* k.initMode (changed)
def initMode (self,event,modeName):

    k = self ; c = k.c
    trace = (False or g.trace_modes) and not g.unitTesting

    if not modeName:
        g.trace('oops: no modeName')
        return
        
    if g.new_modes:
        mode = k.modeController.getMode(modeName)
        if mode:
            mode.initMode()
        else:
            g.trace('***** oops: no mode',modeName)
    else:

        d = g.app.config.modeCommandsDict.get('enter-'+modeName)
        if not d:
            self.badMode(modeName)
            return
        else:
            k.modeBindingsDict = d
            si = d.get('*command-prompt*')
            if si:
                prompt = si.kind # A kludge.
            else:
                prompt = modeName
            if trace: g.trace('modeName',modeName,prompt,'d.keys()',list(d.keys()))
    
        k.inputModeName = modeName
        k.silentMode = False
    
        aList = d.get('*entry-commands*',[])
        if aList:
            for si in aList:
                assert g.isShortcutInfo(si),si
                commandName = si.commandName
                if trace: g.trace('entry command:',commandName)
                k.simulateCommand(commandName)
                # Careful, the command can kill the commander.
                if g.app.quitting or not c.exists: return
                # New in Leo 4.5: a startup command can immediately transfer to another mode.
                if commandName.startswith('enter-'):
                    if trace: g.trace('redirect to mode',commandName)
                    return
    
        # Create bindings after we know whether we are in silent mode.
        w = g.choose(k.silentMode,k.modeWidget,k.w)
        k.createModeBindings(modeName,d,w)
        k.showStateAndMode(prompt=prompt)
.. @+node:ekr.20120208064440.10201: *6* k.NEWgeneralModeHandler (NEW MODES)
def NEWgeneralModeHandler (self,event,
    commandName=None,func=None,modeName=None,nextMode=None,prompt=None):

    '''Handle a mode defined by an @mode node in leoSettings.leo.'''

    k = self ; c = k.c
    state = k.getState(modeName)
    trace = (False or g.trace_modes) and not g.unitTesting

    if trace: g.trace(modeName,'state',state)

    if state == 0:
        k.inputModeName = modeName
        k.modePrompt = prompt or modeName
        k.modeWidget = event and event.widget
        k.setState(modeName,1,handler=k.generalModeHandler)
        self.initMode(event,modeName)
        # Careful: k.initMode can execute commands that will destroy a commander.
        if g.app.quitting or not c.exists: return # (for Tk) 'break'
        if not k.silentMode:
            if c.config.getBool('showHelpWhenEnteringModes'):
                k.modeHelp(event)
            else:
                c.frame.log.hideTab('Mode')
    elif not func:
        g.trace('No func: improper key binding')
        return # (for Tk) 'break'
    else:
        if commandName == 'mode-help':
            func(event)
        else:
            savedModeName = k.inputModeName # Remember this: it may be cleared.
            self.endMode()
            if trace or c.config.getBool('trace_doCommand'): g.trace(func.__name__)
            # New in 4.4.1 b1: pass an event describing the original widget.
            if event:
                event.w = event.widget = k.modeWidget
            else:
                event = g.app.gui.create_key_event(c,None,None,k.modeWidget)
            if trace: g.trace(modeName,'state',state,commandName,'nextMode',nextMode)
            func(event)
            if g.app.quitting or not c.exists:
                return # (for Tk) 'break'
            if nextMode in (None,'none'):
                # Do *not* clear k.inputModeName or the focus here.
                # func may have put us in *another* mode.
                pass
            elif nextMode == 'same':
                silent = k.silentMode
                k.setState(modeName,1,handler=k.generalModeHandler)
                self.reinitMode(modeName) # Re-enter this mode.
                k.silentMode = silent
            else:
                k.silentMode = False # All silent modes must do --> set-silent-mode.
                self.initMode(event,nextMode) # Enter another mode.
                # Careful: k.initMode can execute commands that will destroy a commander.
                if g.app.quitting or not c.exists: return # (for Tk) 'break'

    return # (for Tk) 'break'
.. @+node:ekr.20120228164039.10047: *4* Initing...
.. @+node:ekr.20060102103625.1: *5* doMode (ParserBaseClass)
def doMode(self,p,kind,name,val):

    '''Parse an @mode node and create the enter-<name>-mode command.'''

    trace = False and not g.unitTesting
    c,k = self.c,self.c.k
    
    if g.new_modes:
        aList = []
        for line in g.splitLines(p.b):
            line = line.strip()
            if line and not g.match(line,0,'#'):
                name2,si = self.parseShortcutLine('*mode-setting*',line)
                aList.append((name2,si),)
        k.modeController.makeMode(name,aList)
    else:
        name1 = name

        # g.trace('%20s' % (name),c.fileName())
        modeName = self.computeModeName(name)
    
        d = g.TypedDictOfLists(
            name='modeDict for %s' % (modeName),
            keyType=type('commandName'),valType=g.ShortcutInfo)
    
        s = p.b
        lines = g.splitLines(s)
        for line in lines:
            line = line.strip()
            if line and not g.match(line,0,'#'):
                name,si = self.parseShortcutLine('*mode-setting*',line)
                assert g.isShortcutInfo(si),si
                if not name:
                    # An entry command: put it in the special *entry-commands* key.
                    d.add('*entry-commands*',si)
                elif si is not None:
                    # A regular shortcut.
                    si.pane = modeName
                    aList = d.get(name,[])
                    for z in aList:
                        assert g.isShortcutInfo(z),z
                    # Important: use previous bindings if possible.
                    key2,aList2 = c.config.getShortcut(name)
                    for z in aList2:
                        assert g.isShortcutInfo(z),z
                    aList3 = [z for z in aList2 if z.pane != modeName]
                    if aList3:
                        # g.trace('inheriting',[b.val for b in aList3])
                        aList.extend(aList3)
                    aList.append(si)
                    d.replace(name,aList)
                    
                    if 0: #### Why would we want to do this????
                        #### Old code: we have to save/restore self.shortcutsDict.
                            #### self.set(p,"shortcut",name,aList)
                        # Set the entry directly.
                        d2 = self.shortcutsDict
                        gs = d2.get(key2)
                        if gs:
                            assert g.isGeneralSetting(gs)
                            path = gs.path
                            if c.os_path_finalize(c.mFileName) != c.os_path_finalize(path):
                                g.es("over-riding setting:",name,"from",path)
        
                        # Important: we can't use c here: it may be destroyed!
                        d2 [key2] = g.GeneralSetting(
                            kind,path=c.mFileName,val=val,tag='setting')
    
            # Restore the global shortcutsDict.
            ##### self.shortcutsDict = old_d
            
            if trace: g.trace(d.dump())
        
            # Create the command, but not any bindings to it.
            self.createModeCommand(modeName,name1,d)
.. @+node:ekr.20041120112043: *5* parseShortcutLine (ParserBaseClass)
def parseShortcutLine (self,kind,s):

    '''Parse a shortcut line.  Valid forms:

    --> entry-command
    settingName = shortcut
    settingName ! paneName = shortcut
    command-name --> mode-name = binding
    command-name --> same = binding
    '''

    trace = False and not g.unitTesting and kind == '*mode-setting*'
    c,k = self.c,self.c.k
    assert c
    name = val = nextMode = None ; nextMode = 'none'
    i = g.skip_ws(s,0)

    if g.match(s,i,'-->'): # New in 4.4.1 b1: allow mode-entry commands.
        j = g.skip_ws(s,i+3)
        i = g.skip_id(s,j,'-')
        entryCommandName = s[j:i]
        if trace: g.trace('-->',entryCommandName)
        return None,g.ShortcutInfo('*entry-command*',commandName=entryCommandName)

    j = i
    i = g.skip_id(s,j,'-') # New in 4.4: allow Emacs-style shortcut names.
    name = s[j:i]
    if not name:
        if trace: g.trace('no name',repr(s))
        return None,None

    # New in Leo 4.4b2.
    i = g.skip_ws(s,i)
    if g.match(s,i,'->'): # New in 4.4: allow pane-specific shortcuts.
        j = g.skip_ws(s,i+2)
        i = g.skip_id(s,j)
        nextMode = s[j:i]

    i = g.skip_ws(s,i)
    if g.match(s,i,'!'): # New in 4.4: allow pane-specific shortcuts.
        j = g.skip_ws(s,i+1)
        i = g.skip_id(s,j)
        pane = s[j:i]
        if not pane.strip(): pane = 'all'
    else: pane = 'all'

    i = g.skip_ws(s,i)
    if g.match(s,i,'='):
        i = g.skip_ws(s,i+1)
        val = s[i:]

    # New in 4.4: Allow comments after the shortcut.
    # Comments must be preceded by whitespace.
    comment = ''
    if val:
        i = val.find('#')
        if i > 0 and val[i-1] in (' ','\t'):
            # comment = val[i:].strip()
            val = val[:i].strip()

    stroke = k.strokeFromSetting(val)
    assert g.isStrokeOrNone(stroke),stroke
    # g.trace('stroke',stroke)
    si = g.ShortcutInfo(kind=kind,nextMode=nextMode,pane=pane,stroke=stroke)
    if trace: g.trace('%25s %s' % (name,si))
    return name,si
.. @+node:ekr.20060102103625: *5* createModeCommand (ParserBaseClass)
def createModeCommand (self,modeName,name,modeDict):

    modeName = 'enter-' + modeName.replace(' ','-')

    i = name.find('::')
    if i > -1:
        # The prompt is everything after the '::'
        prompt = name[i+2:].strip()
        modeDict ['*command-prompt*'] = g.ShortcutInfo(kind=prompt)

    # Save the info for k.finishCreate and k.makeAllBindings.
    d = g.app.config.modeCommandsDict

    # New in 4.4.1 b2: silently allow redefinitions of modes.
    d [modeName] = modeDict
    
.. @+node:ekr.20120322073519.10406: *3* Next
There is no real need to do any of these for 4.10 final
.. @+node:ekr.20120322073519.10403: *4* Investigate solarized colors
Withdraw claim that Leo supports solarized.
.. @+node:ekr.20120322073519.10405: *5* Added support for solarized colors
http://ethanschoonover.com/solarized

.. @+node:ekr.20120321174708.9743: *4* Improve help-for-command
Every command should have a **complete** docstring.
.. @+node:ekr.20120327163022.9740: *4* Improve LeoInspect
.. @+node:ekr.20111128103520.10245: *5* Finish leoInspect.token_range
@language python
@language rest

Traverse the tree, setting N.end_lineno and N.end_col_offset.

See the notes below.
.. @+node:ekr.20111128103520.10257: *6* @test leoInspect.token_range (s)
import leo.core.leoInspect as inspect

# g.cls()

def show(o,indent=0):
    pad = ' '*4*indent
    # print('\n%s\n' % o.sd.dump_ast(o.tree()))
    print('%s%s' % (pad,o.format()))
    print('token range: %s' % (repr(o.token_range())))

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

m = inspect.module(s=s)

if 0:
    show(m,0)
    
if 1:
    print('\nAssignments to a...\n')
    for o in m.assignments_to('a'):
        # print(o.format())
        show(o)
if 0:
    print('\nAssignments using d...\n')
    for o in m.assignments_using('d'):
        # print(o.format())
        show(o)
if 0:    
    print('\nCalls to f...\n')
    for o in m.calls_to('f'):
        # print(o.format())
        show(o)
if 0:
    for s in m.statements():
        show(s)
    
    for f in m.functions():
        show(f,0)
        for z in f.statements():
            show(z,1)
    
    for cls in m.classes():
        show(cls,0)
        for d in cls.defs():
            show(d,1)
            for z in d.statements():
                show(z,1)
.. @+node:ekr.20111128103520.10258: *7* << define s >>
s = '''
# x.y = b(arg1,arg2=5,*args,**args).c[1:2:3].d
a=b+c
# f(a=1,b=2,*args,**keys)
'''


# def outer_function(a,b=99,c=88,*args,**keys):
    # print('hello')
    
# class myClass:
    # def method():
        # pass
    
.. @+node:ekr.20111129084537.10357: *6* Notes
@language rest

> All this means that it would likely be too expensive (or just too
> ugly) to compute the data needed for token_range in the base
> AstTraverser class.  Such computations will be performed by traversing
> the module's entire AST the first time "token_range" is called.

There are two parts to the problem.  Happily, neither requires a
separate tree-traversal class.

A token-info prepass
====================

For each node N of a module's tree, we want to inject the following
two new ivars:

- N.end_lineno: the line number of the last character of the token.
- N.end_col_offset: the (byte) offset of the last character of the
token.

After experimenting with a few traversals last night I suddenly
realized tree structure is irrelevant when computing these fields: we
simply want a **sorted** list of (N.lineno,No.col_offset, N) tuples!

The prepass will use ast.walk(root), to generate the list.  After
sorting the list, the prepass will inject inject N.end_lineno and
N.end_col_offset ivars into each node N by stepping through the list.
The ending values of the previous node on the list are the the same as
the beginning values of the next node on the list.

This prepass need only be done once per module.

token_range
===========

To compute token_range for a *particular* N, we want to discover
values M.end_lineno and M.end_col_offset for M, the **last** token in
N's entire tree.

token_range will do the prepass on the modules tree if necessary.
token_range will then call ast.walk(N) to discover all of N's nodes,
sort the list, and return the last element of the list!

In short, token_range is clean and bullet-proof--a happy result.
.. @+node:ekr.20111116103733.10402: *6* << define class Context >>
class Context(object):

    '''The base class of all context-related semantic data.

    All types ultimately resolve to a context.'''

    def __repr__ (self):
        return 'Context: %s' % (self.context_kind)

    __str__ = __repr__

    @others
.. @+node:ekr.20111116103733.10403: *7* cx ctor
def __init__(self,tree,parent_context,sd,kind):

    self.is_temp_context = kind in ['comprehension','for','lambda','with']
    self.context_kind = kind
    # assert kind in ('class','comprehension','def','for','lambda','module','with'),kind
    self.formatter = sd.formatter
    self.parent_context = parent_context
    self.sd = sd
    self.st = SymbolTable(context=self)

    sd.n_contexts += 1

    # Public semantic data: accessed via getters.
    self._classes = [] # Classes defined in this context.
    self._defs = [] # Functions defined in this context.
    self._statements = [] # List of all statements in the context.
    self._tree = tree
    
    # Private semantic data: no getters.
    self.global_names = set() # Names that appear in a global statement in this context.
    self.temp_contexts = [] # List of inner 'comprehension','for','lambda','with' contexts.
    
    # Record the name.ctx contexts.
    self.del_names = set()      # Names with ctx == 'Del'
    self.load_names = set()     # Names with ctx == 'Load'
    self.param_names = set()    # Names with ctx == 'Param'
    self.store_names = set()    # Names with ctx == 'Store'
    
    # Data for the resolution algorithm.
    self.all_global_names = set() # Global names in all parent contexts.
    
    # Compute the class context.
    if self.context_kind == 'module':
        self.class_context = None
    elif self.context_kind == 'class':
        self.class_context = self
    else:
        self.class_context = parent_context.class_context
        
    # Compute the defining context.
    if self.is_temp_context:
        self.defining_context = parent_context.defining_context
    else:
        self.defining_context = self
    
    # Compute the module context.
    if self.context_kind == 'module':
        self.module_context = self
    else:
        self.module_context = parent_context.module_context
.. @+node:ekr.20111116103733.10404: *7* cx.ast_kind
def ast_kind (self,tree):

    return tree.__class__.__name__
.. @+node:ekr.20111116103733.10405: *7* cx.description & name
def description (self):
    
    '''Return a description of this context and all parent contexts.'''
    
    if self.parent_context:
        return  '%s:%s' % (
            self.parent_context.description(),repr(self))
    else:
        return repr(self)

# All subclasses override name.
name = description
.. @+node:ekr.20111116103733.10407: *7* cx.dump
def dump (self,level=0,verbose=False):

    if 0: # Just print the context
        print(repr(self))
    else:
        self.st.dump(level=level)

    if verbose:
        for z in self._classes:
            z.dump(level+1)
        for z in self._defs:
            z.dump(level+1)
        for z in self.temp_contexts:
            z.dump(level+1)
.. @+node:ekr.20111117031039.10099: *7* cx.format
def format(self,brief=True):
    
    cx = self
    
    # return cx.sd.dumper.dumpTreeAsString(cx._tree,brief=brief,outStream=None)
    
    # return ast.dump(cx._tree,annotate_fields=True,include_attributes=not brief)
    
    return AstFormatter().format(cx._tree)
.. @+node:ekr.20111116161118.10113: *7* cx.getters & setters
.. @+node:ekr.20111116161118.10114: *8* cx.assignments & helper
def assignments (self,all=True):
    
    if all:
        return self.all_assignments(result=None)
    else:
        return self.filter_assignments(self._statements)

def all_assignments(self,result):

    if result is None:
        result = []
    result.extend(self.filter_assignments(self._statements))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_assignments(result)
    return result
    
def filter_assignments(self,aList):
    '''Return all the assignments in aList.'''
    return [z for z in aList
        if z.context_kind in ('assn','aug-assn')]
.. @+node:ekr.20111116161118.10115: *8* cx.assignments_to
def assignments_to (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for assn in self.assignments(all=all):
        tree = assn.tree()
        kind = self.ast_kind(tree)
        if kind == 'Assign':
            for target in tree.targets:
                lhs = format(target)
                if s == lhs:
                    result.append(assn)
                    break
        else:
            assert kind == 'AugAssign',kind
            lhs = format(tree.target)
            if s == lhs:
                result.append(assn)

    return result
.. @+node:ekr.20111116161118.10116: *8* cx.assignments_using
def assignments_using (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for assn in self.assignments(all=all):
        tree = assn.tree()
        kind = self.ast_kind(tree)
        assert kind in ('Assign','AugAssign'),kind
        rhs = format(tree.value)
        i = rhs.find(s,0)
        while -1 < i < len(rhs):
            if g.match_word(rhs,i,s):
                result.append(assn)
                break
            else:
                i += len(s)

    return result
.. @+node:ekr.20111126074312.10386: *8* cx.calls & helpers
def calls (self,all=True):
    
    if all:
        return self.all_calls(result=None)
    else:
        return self.filter_calls(self._statements)

def all_calls(self,result):

    if result is None:
        result = []
    result.extend(self.filter_calls(self._statements))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_calls(result)
    return result
    
def filter_calls(self,aList):
    '''Return all the calls in aList.'''
    return [z for z in aList
        if z.context_kind == 'call']
.. @+node:ekr.20111126074312.10384: *8* cx.call_to
def calls_to (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for call in self.calls(all=all):
        tree = call.tree()
        func = format(tree.func)
        if s == func:
            result.append(call)

    return result
.. @+node:ekr.20111126074312.10400: *8* cx.call_args_of
def call_args_of (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for call in self.calls(all=all):
        tree = call.tree()
        func = format(tree.func)
        if s == func:
            result.append(call)

    return result
.. @+node:ekr.20111116161118.10163: *8* cx.classes
def classes (self,all=True):
    
    if all:
        return self.all_classes(result=None)
    else:
        return self._classes

def all_classes(self,result):

    if result is None:
        result = []
    result.extend(self._classes)
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_classes(result)
    return result
.. @+node:ekr.20111116161118.10164: *8* cx.defs
def defs (self,all=True):
    
    if all:
        return self.all_defs(result=None)
    else:
        return self._defs

def all_defs(self,result):

    if result is None:
        result = []
    result.extend(self._defs)
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_defs(result)
    return result
.. @+node:ekr.20111116161118.10152: *8* cx.functions & helpers
def functions (self,all=True):
    
    if all:
        return self.all_functions(result=None)
    else:
        return self.filter_functions(self._defs)

def all_functions(self,result):

    if result is None:
        result = []
    result.extend(self.filter_functions(self._defs))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_functions(result)
    return result
.. @+node:ekr.20111116161118.10223: *9* cx.filter_functions & is_function
def filter_functions(self,aList):
    return [z for z in aList if self.is_function(z)]

def is_function(self,f):
    '''Return True if f is a function, not a method.'''
    return True
.. @+node:ekr.20111126074312.10449: *8* cx.line_number
def line_number (self):
    
    return self._tree.lineno
.. @+node:ekr.20111116161118.10153: *8* cx.methods & helpers
def methods (self,all=True):
    
    if all:
        return self.all_methods(result=None)
    else:
        return self.filter_methods(self._defs)

def all_methods(self,result):

    if result is None:
        result = []
    result.extend(self.filter_methods(self._defs))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_methods(result)
    return result
.. @+node:ekr.20111116161118.10225: *9* cx.filter_functions & is_function
def filter_methods(self,aList):
    return [z for z in aList if self.is_method(z)]

def is_method(self,f):
    '''Return True if f is a method, not a function.'''
    return True
.. @+node:ekr.20111116161118.10165: *8* cx.statements
def statements (self,all=True):
    
    if all:
        return self.all_statements(result=None)
    else:
        return self._statements

def all_statements(self,result):

    if result is None:
        result = []
    result.extend(self._statements)
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_statements(result)
    return result
.. @+node:ekr.20111128103520.10259: *8* cx.token_range
def token_range (self):
    
    tree = self._tree
    
    # return (
        # g.toUnicode(self.byte_array[:tree.col_offset]),
        # g.toUnicode(self.byte_array[:tree_end_col_offset]),
    # )
    
    if hasattr(tree,'col_offset') and hasattr(tree,'end_col_offset'):
        return tree.lineno,tree.col_offset,tree.end_lineno,tree.end_col_offset
    else:
        return -1,-1
.. @+node:ekr.20111116161118.10166: *8* cx.tree
def tree(self):
    
    '''Return the AST (Abstract Syntax Tree) associated with this query object
    (Context Class).  This tree can be passed to the format method for printing.
    '''
    
    return self._tree
.. @+node:ekr.20111116161118.10113: *6* cx.getters & setters
.. @+node:ekr.20111116161118.10114: *7* cx.assignments & helper
def assignments (self,all=True):
    
    if all:
        return self.all_assignments(result=None)
    else:
        return self.filter_assignments(self._statements)

def all_assignments(self,result):

    if result is None:
        result = []
    result.extend(self.filter_assignments(self._statements))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_assignments(result)
    return result
    
def filter_assignments(self,aList):
    '''Return all the assignments in aList.'''
    return [z for z in aList
        if z.context_kind in ('assn','aug-assn')]
.. @+node:ekr.20111116161118.10115: *7* cx.assignments_to
def assignments_to (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for assn in self.assignments(all=all):
        tree = assn.tree()
        kind = self.ast_kind(tree)
        if kind == 'Assign':
            for target in tree.targets:
                lhs = format(target)
                if s == lhs:
                    result.append(assn)
                    break
        else:
            assert kind == 'AugAssign',kind
            lhs = format(tree.target)
            if s == lhs:
                result.append(assn)

    return result
.. @+node:ekr.20111116161118.10116: *7* cx.assignments_using
def assignments_using (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for assn in self.assignments(all=all):
        tree = assn.tree()
        kind = self.ast_kind(tree)
        assert kind in ('Assign','AugAssign'),kind
        rhs = format(tree.value)
        i = rhs.find(s,0)
        while -1 < i < len(rhs):
            if g.match_word(rhs,i,s):
                result.append(assn)
                break
            else:
                i += len(s)

    return result
.. @+node:ekr.20111126074312.10386: *7* cx.calls & helpers
def calls (self,all=True):
    
    if all:
        return self.all_calls(result=None)
    else:
        return self.filter_calls(self._statements)

def all_calls(self,result):

    if result is None:
        result = []
    result.extend(self.filter_calls(self._statements))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_calls(result)
    return result
    
def filter_calls(self,aList):
    '''Return all the calls in aList.'''
    return [z for z in aList
        if z.context_kind == 'call']
.. @+node:ekr.20111126074312.10384: *7* cx.call_to
def calls_to (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for call in self.calls(all=all):
        tree = call.tree()
        func = format(tree.func)
        if s == func:
            result.append(call)

    return result
.. @+node:ekr.20111126074312.10400: *7* cx.call_args_of
def call_args_of (self,s,all=True):
    
    format,result = self.formatter.format,[]

    for call in self.calls(all=all):
        tree = call.tree()
        func = format(tree.func)
        if s == func:
            result.append(call)

    return result
.. @+node:ekr.20111116161118.10163: *7* cx.classes
def classes (self,all=True):
    
    if all:
        return self.all_classes(result=None)
    else:
        return self._classes

def all_classes(self,result):

    if result is None:
        result = []
    result.extend(self._classes)
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_classes(result)
    return result
.. @+node:ekr.20111116161118.10164: *7* cx.defs
def defs (self,all=True):
    
    if all:
        return self.all_defs(result=None)
    else:
        return self._defs

def all_defs(self,result):

    if result is None:
        result = []
    result.extend(self._defs)
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_defs(result)
    return result
.. @+node:ekr.20111116161118.10152: *7* cx.functions & helpers
def functions (self,all=True):
    
    if all:
        return self.all_functions(result=None)
    else:
        return self.filter_functions(self._defs)

def all_functions(self,result):

    if result is None:
        result = []
    result.extend(self.filter_functions(self._defs))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_functions(result)
    return result
.. @+node:ekr.20111116161118.10223: *8* cx.filter_functions & is_function
def filter_functions(self,aList):
    return [z for z in aList if self.is_function(z)]

def is_function(self,f):
    '''Return True if f is a function, not a method.'''
    return True
.. @+node:ekr.20111126074312.10449: *7* cx.line_number
def line_number (self):
    
    return self._tree.lineno
.. @+node:ekr.20111116161118.10153: *7* cx.methods & helpers
def methods (self,all=True):
    
    if all:
        return self.all_methods(result=None)
    else:
        return self.filter_methods(self._defs)

def all_methods(self,result):

    if result is None:
        result = []
    result.extend(self.filter_methods(self._defs))
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_methods(result)
    return result
.. @+node:ekr.20111116161118.10225: *8* cx.filter_functions & is_function
def filter_methods(self,aList):
    return [z for z in aList if self.is_method(z)]

def is_method(self,f):
    '''Return True if f is a method, not a function.'''
    return True
.. @+node:ekr.20111116161118.10165: *7* cx.statements
def statements (self,all=True):
    
    if all:
        return self.all_statements(result=None)
    else:
        return self._statements

def all_statements(self,result):

    if result is None:
        result = []
    result.extend(self._statements)
    for aList in (self._classes,self._defs):
        for cx in aList:
            cx.all_statements(result)
    return result
.. @+node:ekr.20111128103520.10259: *7* cx.token_range
def token_range (self):
    
    tree = self._tree
    
    # return (
        # g.toUnicode(self.byte_array[:tree.col_offset]),
        # g.toUnicode(self.byte_array[:tree_end_col_offset]),
    # )
    
    if hasattr(tree,'col_offset') and hasattr(tree,'end_col_offset'):
        return tree.lineno,tree.col_offset,tree.end_lineno,tree.end_col_offset
    else:
        return -1,-1
.. @+node:ekr.20111116161118.10166: *7* cx.tree
def tree(self):
    
    '''Return the AST (Abstract Syntax Tree) associated with this query object
    (Context Class).  This tree can be passed to the format method for printing.
    '''
    
    return self._tree
.. @+node:ekr.20111128103520.10259: *6* cx.token_range
def token_range (self):
    
    tree = self._tree
    
    # return (
        # g.toUnicode(self.byte_array[:tree.col_offset]),
        # g.toUnicode(self.byte_array[:tree_end_col_offset]),
    # )
    
    if hasattr(tree,'col_offset') and hasattr(tree,'end_col_offset'):
        return tree.lineno,tree.col_offset,tree.end_lineno,tree.end_col_offset
    else:
        return -1,-1
.. @+node:ekr.20120318110848.9724: *5* Make leoInspect a stand-alone module

.. @+node:ekr.20120229094652.15178: *3* Minor
.. @+node:ekr.20111008073427.15595: *4* Must have a shortcut for macro-call-last
.. @+node:ekr.20111114085925.9932: *4* Why does the unittest/output folder contain empty files?
.. @+node:ekr.20031218072017.1462: *5* ic.exportHeadlines
def exportHeadlines (self,fileName):

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

    try:
        theFile = open(fileName,'w')
    except IOError:
        g.es("can not open",fileName,color="blue")
        c.testManager.fail()
        return
    for p in p.self_and_subtree():
        head = p.moreHead(firstLevel,useVerticalBar=True)
        s = head + nl
        if not g.isPython3: # 2010/08/27
            s = g.toEncodedString(s,encoding=self.encoding,reportErrors=True)
        theFile.write(s)
    theFile.close()
.. @+node:ekr.20051104075904.78: *5* TM.makeImportExportSuite
def makeImportExportSuite(self,parentHeadline,doImport):

    """Create an Import/Export test for every descendant of testParentHeadline.."""

    tm = self
    c = self.c
    parent = tm.findNodeAnywhere(parentHeadline)
    assert parent,'node not found: %s' % (parentHeadline)
    temp = tm.findNodeInTree(parent,"tempNode")
    assert temp,'node not found: tempNode'

    # Create the suite and add all test cases.
    suite = unittest.makeSuite(unittest.TestCase)

    for p in parent.children():
        if p != temp:
            # 2009/10/02: avoid copy arg to iter
            p2 = p.copy()
            dialog = tm.findNodeInTree(p2,"dialog")
            assert(dialog)
            test = importExportTestCase(c,p2,dialog,temp,doImport)
            suite.addTest(test)

    return suite
.. @+node:ekr.20051104075904.79: *5* class importExportTestCase
class importExportTestCase(unittest.TestCase):

    """Data-driven unit tests for Leo's edit body commands."""

    @others
.. @+node:ekr.20051104075904.80: *6* __init__ (importExportTestCase)
def __init__ (self,c,p,dialog,temp_p,doImport):

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

    self.c = c
    self.dialog = dialog
    self.p = p.copy()
    self.temp_p = temp_p.copy()

    self.gui = None
    self.oldGui = None
    self.wasChanged = c.changed
    self.fileName = ""
    self.doImport = doImport

    self.old_p = c.p
.. @+node:ekr.20051104075904.81: *6*  fail (importExportTestCase)
def fail (self,msg=None):

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

    import leo.core.leoGlobals as g

    g.app.unitTestDict["fail"] = g.callers()
.. @+node:ekr.20051104075904.82: *6* importExport
def importExport (self):

    c = self.c ; p = self.p

    g.app.unitTestDict = {'c':c,'g':g,'p':p and p.copy()}

    commandName = p.h
    command = getattr(c,commandName) # Will fail if command does not exist.
    command(event=None)

    failedMethod = g.app.unitTestDict.get("fail")
    self.failIf(failedMethod,failedMethod)
.. @+node:ekr.20051104075904.83: *6* runTest
def runTest(self):

    # """Import Export Test Case"""

    self.importExport()
.. @+node:ekr.20051104075904.84: *6* setUp
def setUp(self):

    trace = False
    c = self.c ; temp_p = self.temp_p
    d = self.dialog

    temp_p.setBodyString('')

    # Create a node under temp_p.
    child = temp_p.insertAsLastChild()
    assert(child)
    c.setHeadString(child,"import/export test: " + self.p.h)
    c.selectPosition(child)

    assert(d)
    s = d.bodyString()
    lines = s.split('\n')
    name = lines[0]
    fileName = lines[1]

    # Replace '\\' by os.path.sep in fileName
    try:
        # os.path.sep does not exist in Python 2.2.x.
        sep = os.path.sep
        fileName = fileName.replace('\\',sep)
    except AttributeError:
        fileName = g.os_path_normpath(fileName)

    self.fileName = fileName = g.os_path_finalize_join(g.app.loadDir,"..",fileName)
    if trace: g.trace('(importExportTestCase',fileName)

    if self.doImport:
        theDict = {name: [fileName]}
    else:
        theDict = {name: fileName}

    self.oldGui = g.app.gui
    self.gui = leoGui.unitTestGui(theDict,trace=False)
.. @+node:ekr.20051104075904.85: *6* shortDescription
def shortDescription (self):

    try:
        return "ImportExportTestCase: %s %s" % (self.p.h,self.fileName)
    except Exception:
        return "ImportExportTestCase"
.. @+node:ekr.20051104075904.86: *6* tearDown
def tearDown (self):

    c = self.c ; temp_p = self.temp_p

    if self.gui:
        self.gui.destroySelf()
        self.gui = None

    temp_p.setBodyString("")
    temp_p.clearDirty()

    if not self.wasChanged:
        c.setChanged (False)

    if 1: # Delete all children of temp node.
        while temp_p.firstChild():
            temp_p.firstChild().doDelete()

    g.app.gui = self.oldGui
    c.selectPosition(self.old_p)
.. @+node:ekr.20031218072017.2850: *5* c.exportHeadlines
def exportHeadlines (self,event=None):

    '''Export all headlines to an external file.'''

    c = self

    filetypes = [("Text files", "*.txt"),("All files", "*")]

    fileName = g.app.gui.runSaveFileDialog(
        initialfile="headlines.txt",
        title="Export Headlines",
        filetypes=filetypes,
        defaultextension=".txt")
    c.bringToFront()

    if fileName and len(fileName) > 0:
        g.setGlobalOpenDir(fileName)
        g.chdir(fileName)
        c.importCommands.exportHeadlines(fileName)
.. @+node:ekr.20120228164039.11406: *4* Clean up #### in leoKeys
.. @+node:ekr.20120226131923.10221: *4* Allow abbreviations in minibuffer
@nocolor-node

cfa=clone-find-all-flattened
.. @+node:ekr.20111125072438.10214: *4* (Maybe) Fix bug: Another @shadow bug (test4.leo)
@nocolor-node

email:
http://mail.google.com/mail/#label/Leo%2FBugs/133ac31add8b2c6c

see attachment, shadow file won't get written correctly, and it won't reread
correctly too. Using leo-editor-snapshot- 201111090253.

Hopefully I will be able to look on some bugs myself, but don't know when. Needs
a bit of time, I am not very familiar with python.

===== EKR

Leo doesn't understand .jsp.  @shadow should refuse to work for unknown languages.
.. @+node:ekr.20031218072017.368: *5* << define global data structures >> (leoApp.py)
# Internally, lower case is used for all language names.
self.language_delims_dict = {
    # Keys are languages, values are 1,2 or 3-tuples of delims.
    "ada"           : "--",
    "batch"         : "REM_", # Use the REM hack.
    "actionscript"  : "// /* */", #jason 2003-07-03
    "autohotkey"    : "; /* */", #TL - AutoHotkey language
    "c"             : "// /* */", # C, C++ or objective C.
    "config"        : "#", # Leo 4.5.1
    "csharp"        : "// /* */", # C#
    "cpp"           : "// /* */",# C++.
    "css"           : "/* */", # 4/1/04
    "cweb"          : "@q@ @>", # Use the "cweb hack"
    "cython"        : "#",
    "elisp"         : ";",
    "forth"         : "\\_ _(_ _)", # Use the "REM hack"
    "fortran"       : "C",
    "fortran90"     : "!",
    "haskell"       : "--_ {-_ _-}",
    "haxe"          : "//",
    "html"          : "<!-- -->",
    "ini"           : ";",
    "java"          : "// /* */",
    "javascript"    : "// /* */", # EKR: 2011/11/12: For javascript import test.
    "javaserverpage": "<%-- --%>", # EKR: 2011/11/25
    "kshell"        : "#", # Leo 4.5.1.
    "latex"         : "%",
    "lisp"          : ";", # EKR: 2010/09/29
    "lua"           : "--",  # ddm 13/02/06
    "matlab"        : "%", # EKR: 2011/10/21
    "nsi"           : ";", # EKR: 2010/10/27
    "noweb"         : "%", # EKR: 2009-01-30. Use Latex for doc chunks.
    "pascal"        : "// { }",
    "perl"          : "#",
    "perlpod"       : "# __=pod__ __=cut__", # 9/25/02: The perlpod hack.
    "php"           : "// /* */", # 6/23/07: was "//",
    "plain"         : "#", # We must pick something.
    "plsql"         : "-- /* */", # SQL scripts qt02537 2005-05-27
    "python"        : "#",
    "rapidq"        : "'", # fil 2004-march-11
    "rebol"         : ";",  # jason 2003-07-03
    "rest"          : ".._",
    "rst"           : ".._",
    "ruby"          : "#",  # thyrsus 2008-11-05
    "scala"         : "// /* */",
    "shell"         : "#",  # shell scripts
    "tcltk"         : "#",
    "tex"           : "%", # Bug fix: 2008-1-30: Fixed Mark Edginton's bug.
    "unknown"       : "#", # Set when @comment is seen.
    "unknown_language" : '#--unknown-language--',
        # For unknown extensions in @shadow files.
    "vim"           : "\"",
    "vimoutline"    : "#",  #TL 8/25/08 Vim's outline plugin
    "xml"           : "<!-- -->",
    "xslt"          : "<!-- -->",
}

# Used only by c.getOpenWithExt.
self.language_extension_dict = {
    # Keys are languages, values are extensions.
    "ada"           : "ada",
    "actionscript"  : "as", #jason 2003-07-03
    "autohotkey"    : "ahk", #TL - AutoHotkey language
    "batch"         : "bat", # Leo 4.5.1.
    "c"             : "c",
    "config"        : "cfg",
    "cpp"           : "cpp",
    "css"           : "css", # 4/1/04
    "cweb"          : "w",
    #"cython"        : "pyd",
    #"cython"        : "pyi",
    "cython"        : "pyx", # Only one extension is valid at present.
    "elisp"         : "el",
    "forth"         : "forth",
    "fortran"       : "f",
    "fortran90"     : "f90",
    "haskell"       : "hs",
    "haxe"          : "hx",
    "html"          : "html",
    "ini"           : "ini",
    "java"          : "java",
    "javascript"    : "js", # EKR: 2011/11/12: For javascript import test.
    "javaserverpage": "jsp", # EKR: 2011/11/25
    "kshell"        : "ksh", # Leo 4.5.1.
    "latex"         : "tex", # 1/8/04
    "lua"           : "lua",  # ddm 13/02/06
    "matlab"        : "m", # EKR: 2011/10/21
    "nsi"           : "nsi", # EKR: 2010/10/27
    "noweb"         : "nw",
    "pascal"        : "p",
    "perl"          : "pl",      # 11/7/05
    "perlpod"       : "pod",  # 11/7/05
    "php"           : "php",
    "plain"         : "txt",
    "python"        : "py",
    "plsql"         : "sql", # qt02537 2005-05-27
    "rapidq"        : "bas", # fil 2004-march-11
    "rebol"         : "r",    # jason 2003-07-03
    # "rst"           : "rst", # caught by pylint.
    "rst"           : "rest",
    "ruby"          : "rb",   # thyrsus 2008-11-05
    "scala"         : "scala",
    "shell"         : "sh",   # DS 4/1/04
    "tex"           : "tex",
    "tcltk"         : "tcl",
    "unknown"       : "txt", # Set when @comment is seen.
    "vim"           : "vim",
    "vimoutline"    : "otl",  #TL 8/25/08 Vim's outline plugin
    "xml"           : "xml",
    "xslt"          : "xsl",
}

self.extension_dict = {
    # Keys are extensions, values are languages.
    "ada"   : "ada",
    "adb"   : "ada",
    "ahk"   : "autohotkey",  # EKR: 2009-01-30.
    "as"    : "actionscript",
    "bas"   : "rapidq",
    "bat"   : "batch",
    "c"     : "c",
    "cfg"   : "config",
    "cpp"   : "cpp",
    "css"   : "css",
    "el"    : "elisp",
    "forth" : "forth",
    "f"     : "fortran",
    "f90"   : "fortran90",
    "h"     : "c",
    "html"  : "html",
    "hs"    : "haskell",
    "ini"   : "ini",
    "java"  : "java",
    "js"    : "javascript", # EKR: 2011/11/12: For javascript import test.
    "jsp"   : "javaserverpage", # EKR: 2011/11/25: For @shadow.
    "ksh"   : "kshell", # Leo 4.5.1.
    "lua"   : "lua",  # ddm 13/02/06
    "m"     : "matlab", # EKR 2011/10/21
    "nsi"   : "nsi", # EKR: 2010/10/27
    "nw"    : "noweb",
    "otl"   : "vimoutline",  #TL 8/25/08 Vim's outline plugin
    "p"     : "pascal",
    "pl"    : "perl",   # 11/7/05
    "pod"   : "perlpod", # 11/7/05
    "php"   : "php",
    "py"    : "python",
    "pyd"   : "cython",
    "pyi"   : "cython",
    "pyx"   : "cython",
    "sql"   : "plsql", # qt02537 2005-05-27
    "r"     : "rebol",
    "rb"    : "ruby", # thyrsus 2008-11-05
    "rest"  : "rst",
    "rst"   : "rst",
    "scala" : "scala",
    "sh"    : "shell",
    "tex"   : "tex",
    "txt"   : "plain",
    "tcl"   : "tcltk",
    "vim"   : "vim",
    "w"     : "cweb",
    "xml"   : "xml",
    "xsl"   : "xslt",
    "hx"    : "haxe",
}

# Extra language extensions, used to associate extensions with mode files.
# Used by importCommands.languageForExtension.
# Keys are extensions, values are corresponding mode file (without .py)
# A value of 'none' is a signal to unit tests that no extension file exists.
self.extra_extension_dict = {
    'actionscript': 'actionscript',
    'ada'   : 'ada95',
    'adb'   : 'none', # ada??
    'awk'   : 'awk',
    'bas'   : 'none', # rapidq
    'bat'   : 'none', # batch
    'cfg'   : 'none', # Leo 4.5.1
    'cpp'   : 'c',
    'el'    : 'lisp',
    'f'     : 'fortran90',
    'hx'    : 'none',
    'ksh'   : 'none', # Leo 4.5.1
    'nsi'   : 'none', # Leo 4.8.
    'nw'    : 'none', # noweb.
    'otl'   : 'none', # vimoutline.
    'pod'   : 'perl',
    'tcl'   : 'tcl',
    'unknown_language': 'none',
    'w'     : 'none', # cweb
}

self.global_commands_dict = {}
.. @+node:ekr.20120305084218.9915: *4* Show all commands after <alt-x><tab>
.. @+node:ekr.20120226183512.10195: *3* Bugs
@language rest

Most of these will be moved to the Leo 4.10.1 list.
.. @+node:ekr.20120313134250.9853: *4* First
.. @+node:ekr.20120327163022.9744: *5* The @auto script *must* put @first nodes in root node
@nocolor-node

Also the import commands.

Especially:
    - Shebang line.
    - Python encoding line.
.. @+node:ekr.20111222113610.10245: *5* Fix bug 711158: Warn if same .leo file open in another Leo instance
See https://bugs.launchpad.net/leo-editor/+bug/711158
.. @+node:ekr.20111025141618.16484: *5* Fix bug 879338: Global tables in leoApp.py should describe all languages known to the colorizer
@nocolor-node

See https://bugs.launchpad.net/leo-editor/+bug/879338

Having the colorizer colorize a language properly gives the false illusion that Leo "understands" the language.

Supporting the language in the global tables in leoApp.py makes the illusion a reality.
.. @+node:ekr.20120308120323.9864: *5* Fix bug 903640: Import of Python files containing the strings "<<" and ">>" does not work
@nocolor-node

See: https://bugs.launchpad.net/leo-editor/+bug/903640
Import of Python files containing the strings "<<" and ">>" does not work

When I tried to import a Python file using @auto, the importer didn't
like the following statement:

    self.cprint("<<" + ret + ">>\n")

The respective error message reads:

undefined section: <<" + ret + ">>
referenced from: @file [...]

=====

If you can't change the external file, you won't be able to use @auto,
at least until Leo's @auto import mechanism supports other delimiters
for section references.

As discussed in a recent thread, there is no way to allow escape
conventions for section references: the approach used by noweb will
not work in Leo.

I'll leave this bug open as an important wish-list item reminding me
that it would be good to allow *different* delimiters for section
references on an external-file-by-external-file basis.

=====

Import (and @auto) are a separate matter from the main read code used
for @file. The @file read code knows *nothing* about syntax, except
for comment delimiters. That makes the @file read code simple and
robust.

It *would* be possible to have a Leo directive change (or disable)
delimiters for section references. The code would be straightforward,
similar to the code for the @delims directive. This is what I intend
to do.

.. @+node:ekr.20111123095018.13632: *5* Fix bug: expand Find tab as needed for bigger fonts
.. @+node:ekr.20110605121601.18326: *6* createTab (leoQtLog)
def createTab (self,tabName,widget=None,wrap='none'):
    """ Create a new tab in tab widget

    if widget is None, Create a QTextBrowser,
    suitable for log functionality.
    """

    trace = False and not g.unitTesting
    c = self.c
    
    if trace: g.trace(tabName,widget and g.app.gui.widget_name(widget) or '<no widget>')

    if widget is None:

        widget = LeoQTextBrowser(parent=None,c=c,wrapper=self)
            # widget is subclass of QTextBrowser.
        contents = leoQTextEditWidget(widget=widget,name='log',c=c)
            # contents a wrapper.
        widget.leo_log_wrapper = contents
            # Inject an ivar into the QTextBrowser that points to the wrapper.
            
        if trace: g.trace('** creating',tabName,'self.widget',contents,'wrapper',widget)
        
        widget.setWordWrapMode(
            g.choose(self.wrap,
                QtGui.QTextOption.WordWrap,
                QtGui.QTextOption.NoWrap))

        widget.setReadOnly(False) # Allow edits.
        self.logDict[tabName] = widget
        if tabName == 'Log':
            self.widget = contents # widget is an alias for logCtrl.
            widget.setObjectName('log-widget')

        if True: # 2011/05/28.
            # Set binding on all text widgets.
            theFilter = leoQtEventFilter(c,w=self,tag='log')
            self.eventFilters.append(theFilter) # Needed!
            widget.installEventFilter(theFilter)
            
        if True and tabName == 'Log':
    
            assert c.frame.top.__class__.__name__ == 'DynamicWindow'
            find_widget = c.frame.top.leo_find_widget
            
            # 2011/11/21: A hack: add an event filter.
            find_widget.leo_event_filter = leoQtEventFilter(c,w=widget,tag='find-widget')
            find_widget.installEventFilter(find_widget.leo_event_filter)
            if trace: g.trace('** Adding event filter for Find',find_widget)
            
            # 2011/11/21: A hack: make the find_widget an official log widget.
            self.contentsDict['Find']=find_widget
    
        self.contentsDict[tabName] = widget
        self.tabWidget.addTab(widget,tabName)
    else:
        contents = widget
            # Unlike text widgets, contents is the actual widget.
        widget.leo_log_wrapper = contents
            # The leo_log_wrapper is the widget itself.
        if trace: g.trace('** using',tabName,widget)
        
        if 1: # Now seems to work.
            theFilter = leoQtEventFilter(c,w=contents,tag='tabWidget')
            self.eventFilters.append(theFilter) # Needed!
            widget.installEventFilter(theFilter)
    
        self.contentsDict[tabName] = contents
        self.tabWidget.addTab(contents,tabName)

    return contents
.. @+node:ekr.20110605121601.18166: *6* createFindTab (DynamicWindow)
def createFindTab (self,parent,tab_widget):

    c,dw = self.leo_c,self
    grid = self.createGrid(parent,'findGrid',margin=10,spacing=10)
    grid.setColumnStretch(0,100)
    grid.setColumnStretch(1,100)
    
    # Row 0: heading.
    lab1 = self.createLabel(parent,'findHeading','Find/Change Settings...')
    grid.addWidget(lab1,0,0,1,2,QtCore.Qt.AlignHCenter)
    
    # Rows 1, 2: the find/change boxes, now disabled.
    findPattern = self.createLineEdit(parent,'findPattern',disabled=True)
    findChange  = self.createLineEdit(parent,'findChange',disabled=True)
    lab2 = self.createLabel(parent,'findLabel','Find:')
    lab3 = self.createLabel(parent,'changeLabel','Change:')
    grid.addWidget(lab2,1,0)
    grid.addWidget(lab3,2,0)
    grid.addWidget(findPattern,1,1)
    grid.addWidget(findChange,2,1)
        
    # Check boxes and radio buttons.
    # Radio buttons are mutually exclusive because they have the same parent.
    def mungeName(name):
        # The value returned here is significant: it creates an ivar.
        return 'checkBox%s' % label.replace(' ','').replace('&','')

    # Rows 3 through 8...
    table = (
        ('box', 'Whole &Word',      2,0),
        ('rb',  '&Entire Outline',  2,1),
        ('box', '&Ignore Case',     3,0),
        ('rb',  '&Suboutline Only', 3,1),
        ('box', 'Wrap &Around',     4,0),
        ('rb',  '&Node Only',       4,1),
        # ('box', '&Reverse',       5,0),
        ('box', 'Search &Headline', 5,1),
        ('box', 'Rege&xp',          5,0), # was 6,0
        ('box', 'Search &Body',     6,1),
        ('box', 'Mark &Finds',      6,0), # was 7,0
        ('box', 'Mark &Changes',    7,0)) # was 7,1
        # a,b,c,e,f,h,i,n,rs,w

    for kind,label,row,col in table:
        name = mungeName(label)
        func = g.choose(kind=='box',
            self.createCheckBox,self.createRadioButton)
        w = func(parent,name,label)
        grid.addWidget(w,row+1,col)
        setattr(self,name,w)

    # Row 9: Widgets that take all additional vertical space.
    w = QtGui.QWidget()
    grid.addWidget(w,9,0)
    grid.addWidget(w,9,1)
    grid.setRowStretch(9,100)

    # Official ivars (in addition to setattr ivars).
    self.leo_find_widget = tab_widget # 2011/11/21: a scrollArea.
    self.findPattern = findPattern
    self.findChange = findChange
    # self.findLab = lab2
    # self.changeLab = lab3
.. @+node:ekr.20110605121601.18156: *6* createGrid (DynamicWindow)
def createGrid (self,parent,name,margin=0,spacing=0):

    w = QtGui.QGridLayout(parent)
    w.setMargin(margin)
    w.setSpacing(spacing)
    self.setName(w,name)
    return w
.. @+node:ekr.20110605121601.18145: *6* createLogPane
def createLogPane (self,parent):

    # Create widgets.
    logFrame = self.createFrame(parent,'logFrame',
        vPolicy = QtGui.QSizePolicy.Minimum)
    innerFrame = self.createFrame(logFrame,'logInnerFrame',
        hPolicy=QtGui.QSizePolicy.Preferred,
        vPolicy=QtGui.QSizePolicy.Expanding)
    tabWidget = self.createTabWidget(innerFrame,'logTabWidget')

    # Pack.
    innerGrid = self.createGrid(innerFrame,'logInnerGrid')
    innerGrid.addWidget(tabWidget, 0, 0, 1, 1)
    outerGrid = self.createGrid(logFrame,'logGrid')
    outerGrid.addWidget(innerFrame, 0, 0, 1, 1)
    
    # 2011/10/01: Embed the Find tab in a QScrollArea.
    findScrollArea = QtGui.QScrollArea()
    findScrollArea.setObjectName('findScrollArea')
    findTab = QtGui.QWidget()
    findTab.setObjectName('findTab')
    tabWidget.addTab(findScrollArea,'Find')
    self.createFindTab(findTab,findScrollArea)
    findScrollArea.setWidget(findTab)

    spellTab = QtGui.QWidget()
    spellTab.setObjectName('spellTab')
    tabWidget.addTab(spellTab,'Spell')
    self.createSpellTab(spellTab)

    tabWidget.setCurrentIndex(1)

    # Official ivars
    self.tabWidget = tabWidget # Used by leoQtLog.
.. @+node:ekr.20120226183512.10197: *4* Numbered bugs
.. @+node:ekr.20120313134250.9854: *5* Other
.. @+node:ekr.20111222113610.10244: *6* Fix bug 613153: unable to describe root directory on thumb drive
@nocolor-node

A very complex bug

See https://bugs.launchpad.net/leo-editor/+bug/613153
.. @+node:ekr.20111026091322.16498: *6* Fix bug 869098: Context menu settings lost if save as used
@nocolor-node

See https://bugs.launchpad.net/leo-editor/+bug/869098

I I have a Leo file with settings for context menu:
    
@settings
   @data contextmenu_commands
       edit-headline Edit headline
          copy-node Copy Node
          cut-node Cut Node
          paste-node Paste Node

then these settings are lost if I save the leo file to a new name using
File - Save as
If I reload the leo file, they do return
.. @+node:ekr.20111125072438.10204: *6* Fix bug 882243: (Wishlist) Clones sometimes not saved: change how @others works
@nocolor-node

See https://bugs.launchpad.net/leo-editor/+bug/882243

In Leo, I made this file:

@file test.txt
@others
.....test1
     test: these are clones
.....test2
     @others

      what is going on...?
..........test1 <--- this is cloned
          test: these are clones

The output file does not have the cloned node:

#@+leo-ver=5-thin
#@+node:bill.20111025150533.3528: * @thin test.txt
#@+others
#@+node:bill.20111025150533.3527: ** test1
test: these are clones
#@+node:bill.20111025150533.3529: ** test2
#@+others
#@-others

what is going on...?
#@-others
#@-leo

I don't think it is a conflict with @others; it is just that some files do not retain the clones that are displayed.

Here is the copied node:

<?xml version="1.0" encoding="utf-8"?>
<!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
<?xml-stylesheet ekr_test?>
<leo_file xmlns:leo="http://www.leo-editor.org/2011/leo" >
<leo_header file_format="2"/>
<vnodes>
<v t="bill.20111025150533.3528" a="E"><vh>@thin test.txt</vh>
<v t="bill.20111025150533.3527"><vh>test1</vh></v>
<v t="bill.20111025150533.3529" a="E"><vh>test2</vh>
<v t="bill.20111025150533.3527"></v>
</v>
</v>
</vnodes>
<tnodes>
<t tx="bill.20111025150533.3527">test: these are clones
</t>
<t tx="bill.20111025150533.3528">@others
</t>
<t tx="bill.20111025150533.3529">@others

what is going on...?</t>
</tnodes>
</leo_file>

Here is the copied leo file:

<?xml version="1.0" encoding="utf-8"?>
<!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
<?xml-stylesheet ekr_test?>
<leo_file xmlns:leo="http://www.leo-editor.org/2011/leo" >
<leo_header file_format="2" tnodes="0" max_tnode_index="0" clone_windows="0"/>
<globals body_outline_ratio="0.5" body_secondary_ratio="0.5">
 <global_window_position top="50" left="50" height="500" width="700"/>
 <global_log_window_position top="0" left="0" height="0" width="0"/>
</globals>
<preferences/>
<find_panel_settings/>
<vnodes>
<v t="bill.20111025150533.3528" a="E"
expanded="bill.20111025150533.3529,"><vh>@thin test.txt</vh></v>
</vnodes>
<tnodes>
</tnodes>
</leo_file>
.. @+node:ekr.20111216105907.10218: *7* My response: this is a wishlist item
@nocolor-node

Reposted from http://groups.google.com/group/leo-editor/browse_thread/thread/67a28984616d09c9
About bug 882243: clones sometimes not saved

I'd like to discuss this bug here, because I would like us all to be aware of the situation, and possible changes.

The surprise
=========

To paraphrase the original bug report, suppose we have the following @file tree:

+ @file test.txt
@others
  + node 1 (cloned)
    node 1 text.
  + node 2
     @others
    + node 1 (cloned)
       node 1 text.

As usual, lines preceded by "+" denote headlines: all other lines are body text.

The surprise is that the cloned node1 node is written to the external file only once (as a child of the root node) and hence does not appear as a (cloned) child of the node2 node when Leo next loads the @file tree.

History
=====

I would like to call this a wishlist item because the
present code quite intentionally writes *any* node
(including cloned nodes) only once. That is, the write code
sets a bit when writing a node, and @others ignores any
nodes with that bit set.

You could call this a bug in the @others write logic, but at
one time it was done explicitly and on purpose.

I don't remember why this was so, but I do remember it *was*
so. It may have been an artifact of Leo's old representation
of clones that used both vnodes and tnodes. tnodes no longer
exist in the one-node world that we have been living in for
several years, and it may well be time to revisit the
original design, but I would rather not do that just now
because there are several "real" bugs that need attention
asap.

Workaround
=========

Because this is an issue involving @others, you might assume
that a workaround involving sections would be possible. You
would be correct. The following file works as expected::

+ @file test.txt
<< node 1 >>
<< node 2 >>
  + << node 1>> (cloned)
     node 1 text.
  + << node 2 >>
     << node 1 >>
    + << node 1 >>(cloned)
       node 1 text.
.. @+node:ekr.20111122080923.10186: *6* Fix bug 893223: Unpredictable slow operation state cured by writing
@nocolor-node

Leo-editor unpredictably gets into a state where Ctrl-V fails to paste
and every Leo-editor operation takes much longer than usual.  This
resulted in the following red message in the log pane:

ignoring command: already executing a command.

This bad state was ended by writing the .leo file so it was no longer
dirty.  I've noticed this problem off and on for at least several weeks.

Test Conditions:

Leo-Editor Rev 4832
Leo Log Window
Leo 4.9.1 devel, build 4669, 4Q/2011
Python 2.7.2, qt version 4.7.3
linux2
setting leoID from os.getenv('USER'): 'bob'
load dir: /home/bob/bzrWork/pluginPath/leo/core
global config dir: /home/bob/bzrWork/pluginPath/leo/config
home dir: /home/bob
reading settings in /home/bob/bzrWork/pluginPath/leo/config/leoSettings.leo
reading settings in /home/bob/.leo/myLeoSettings.leo
reading settings in /media/sda1/BobH/1/Leo/Leo.leo
reading: /media/sda1/BobH/1/Leo/Leo.leo
reading: @auto /home/bob/.leo/.leoRecentFiles.txt

EKR =======================

There is a blunder in c.executeScript.  It jams False into c.inCommand
without saving and restoring the previous value.  This has to be
wrong.  I'm not sure that this is the entire fix.  I'll put it on the
urgent list.
.. @+node:ekr.20111122080923.10188: *7* Found: inCommand
.. @+node:ekr.20031218072017.2140: *8* c.executeScript & helpers
def executeScript(self,event=None,args=None,p=None,script=None,
    useSelectedText=True,define_g=True,define_name='__main__',silent=False,
    namespace=None):

    """This executes body text as a Python script.

    We execute the selected text, or the entire body text if no text is selected."""

    c = self ; script1 = script
    if not script:
        script = g.getScript(c,p,useSelectedText=useSelectedText)
    self.redirectScriptOutput()
    try:
        oldLog = g.app.log # 2011/01/19
        log = c.frame.log
        g.app.log = log
        if script.strip():
            sys.path.insert(0,c.frame.openDirectory)
            script += '\n' # Make sure we end the script properly.
            try:
                p = c.p
                if p: c.setCurrentDirectoryFromContext(p)
                d = g.choose(define_g,{'c':c,'g':g,'p':p},{})
                if define_name: d['__name__'] = define_name
                d['script_args'] = args or []
                if namespace: d.update(namespace)
                # if args: sys.argv = args
                # A kludge: reset c.inCommand here to handle the case where we *never* return.
                # (This can happen when there are multiple event loops.)
                # This does not prevent zombie windows if the script puts up a dialog...
                c.inCommand = False
                if c.write_script_file:
                    scriptFile = self.writeScriptFile(script)
                    
                    # 2011/10/31: make g.inScript a synonym for g.app.inScript.
                    g.inScript = g.app.inScript = True
                    try:
                        if g.isPython3:
                            exec(compile(script,scriptFile,'exec'),d)
                        else:
                            execfile(scriptFile,d)
                    finally:
                        g.inScript = g.app.inScript = False
                else:
                    g.app.inScript = True
                    try:
                        exec(script,d)
                    finally:
                        g.app.inScript = False
                if 0: # This message switches panes, and can be disruptive.
                    if not script1 and not silent:
                        # Careful: the script may have changed the log tab.
                        tabName = log and hasattr(log,'tabName') and log.tabName or 'Log'
                        g.es("end of script",color="purple",tabName=tabName)
            except Exception:
                g.handleScriptException(c,p,script,script1)
            del sys.path[0]
        else:
            tabName = log and hasattr(log,'tabName') and log.tabName or 'Log'
            g.es("no script selected",color="blue",tabName=tabName)
    finally:
        g.app.log = oldLog # 2011/01/19
        self.unredirectScriptOutput()
.. @+node:ekr.20031218072017.2143: *9* redirectScriptOutput
def redirectScriptOutput (self):

    c = self

    # g.trace('original')

    if c.config.redirect_execute_script_output_to_log_pane:

        g.redirectStdout() # Redirect stdout
        g.redirectStderr() # Redirect stderr
.. @+node:ekr.20110522121957.18230: *9* setCurrentDirectoryFromContext
def setCurrentDirectoryFromContext(self,p):
    
    trace = False and not g.unitTesting
    c = self
    
    aList = g.get_directives_dict_list(p)
    path = c.scanAtPathDirectives(aList)
    
    curDir = g.os_path_abspath(os.getcwd())

    # g.trace(p.h,'\npath  ',path,'\ncurDir',curDir)
    
    if path and path != curDir:
        if trace: g.trace('calling os.chdir(%s)' % (path))
        try:
            os.chdir(path)
        except Exception:
            pass
.. @+node:EKR.20040627100424: *9* unredirectScriptOutput
def unredirectScriptOutput (self):

    c = self

    # g.trace('original')

    if c.exists and c.config.redirect_execute_script_output_to_log_pane:

        g.restoreStderr()
        g.restoreStdout()
.. @+node:ekr.20031218072017.2817: *8*  c.doCommand
command_count = 0

def doCommand (self,command,label,event=None):

    """Execute the given command, invoking hooks and catching exceptions.

    The code assumes that the "command1" hook has completely handled the command if
    g.doHook("command1") returns False.
    This provides a simple mechanism for overriding commands."""

    c = self ; p = c.p
    commandName = command and command.__name__
    c.setLog()

    self.command_count += 1
    if not g.app.unitTesting and c.config.getBool('trace_doCommand'):
        g.trace(commandName)

    # The presence of this message disables all commands.
    if c.disableCommandsMessage:
        g.es(c.disableCommandsMessage,color='blue')
        return # (for Tk) 'break' # Inhibit all other handlers.

    if c.exists and c.inCommand and not g.unitTesting:
        # g.trace('inCommand',c)
        g.app.commandInterruptFlag = True
        g.es('ignoring command: already executing a command.',color='red')
        return # (for Tk) 'break'

    g.app.commandInterruptFlag = False

    if label and event is None: # Do this only for legacy commands.
        if label == "cantredo": label = "redo"
        if label == "cantundo": label = "undo"
        g.app.commandName = label

    if not g.doHook("command1",c=c,p=p,v=p,label=label):
        try:
            c.inCommand = True
            val = command(event)
            if c and c.exists: # Be careful: the command could destroy c.
                c.inCommand = False
                c.k.funcReturn = val
            # else: g.pr('c no longer exists',c)
        except Exception:
            c.inCommand = False
            if g.app.unitTesting:
                raise
            else:
                g.es_print("exception executing command")
                g.es_exception(c=c)

        if c and c.exists:
            if c.requestCloseWindow:
                g.trace('Closing window after command')
                c.requestCloseWindow = False
                g.app.closeLeoWindow(c.frame)
            else:
                c.outerUpdate()

    # Be careful: the command could destroy c.
    if c and c.exists:
        p = c.p
        g.doHook("command2",c=c,p=p,v=p,label=label)

    return # (for Tk) "break" # Inhibit all other handlers.
.. @+node:ekr.20061031131434.160: *8* k.enterNamedMode
def enterNamedMode (self,event,commandName):

    k = self ; c = k.c
    modeName = commandName[6:]
    c.inCommand = False # Allow inner commands in the mode.
    k.generalModeHandler(event,modeName=modeName)
.. @+node:ekr.20110605121601.18140: *8* closeEvent (DynanicWindow)
def closeEvent (self,event):

    trace = False and not g.unitTesting
    c = self.leo_c

    if not c.exists:
        # Fixes double-prompt bug on Linux.
        if trace: g.trace('destroyed')
        event.accept()
        return

    if c.inCommand:
        if trace: g.trace('in command')
        c.requestCloseWindow = True
    else:
        if trace: g.trace('closing')
        ok = g.app.closeLeoWindow(c.frame)
        if ok:
            event.accept()
        else:
            event.ignore()
.. @+node:ekr.20110605121601.18286: *8* frame.OnCloseLeoEvent
# Called from quit logic and when user closes the window.
# Returns True if the close happened.

def OnCloseLeoEvent(self):

    f = self ; c = f.c

    if c.inCommand:
        # g.trace('requesting window close')
        c.requestCloseWindow = True
    else:
        g.app.closeLeoWindow(self)
.. @+node:ekr.20111123043847.9981: *7* Found: set_trace
.. @+node:ekr.20110310093050.14293: *8* pdb (codewise)
def pdb (message=''):

    """Fall into pdb."""

    import pdb # Required: we have just defined pdb as a function!

    if message:
        print(message)
    pdb.set_trace()
.. @+node:ekr.20041105091148: *8* g.pdb
def pdb (message=''):

    """Fall into pdb."""

    import pdb # Required: we have just defined pdb as a function!

    if message:
        print(message)
    pdb.set_trace()
.. @+node:ekr.20110605121601.18134: *8* init (qtGui.py top level)
def init():
    
    trace = (False or g.trace_startup) and not g.unitTesting
    if trace and g.trace_startup: print('qtGui.__init__')

    if g.app.unitTesting: # Not Ok for unit testing!
        return False

    if not QtCore:
        return False

    if g.app.gui:
        return g.app.gui.guiName() == 'qt'
    else:
        g.app.gui = leoQtGui()

        # Override g.pdb
        def qtPdb(message=''):
            if message: print(message)
            import pdb
            if not g.app.useIpython:
                QtCore.pyqtRemoveInputHook()
            pdb.set_trace()
        g.pdb = qtPdb

        g.app.gui.finishCreate()
        g.plugin_signon(__name__)
        return True
.. @+node:ekr.20120229094652.15181: *6* Fix bug 903470: print-bindings no longer properly terminates each line
@language rest

See https://bugs.launchpad.net/leo-editor/+bug/903470
print-bindings no longer properly terminates each line

This is not a print-binding bug per se, but a bug
in bindings in the "Bindings" tab:

Everything works on windows, but there are several problems on Ubuntu:

1. The contextmenu plugin doesn't show many options.
2. As you say, cutting and pasting does not preserve newlines.
.. @+node:ekr.20111026091322.16503: *4* Other Bugs
.. @+node:ekr.20120318124102.9739: *5* Focus border doesn't work with Python 2.x!
.. @+node:ekr.20111022215436.16685: *6* Borders (qtGui)
def add_border(self,c,w):

    if c.use_focus_border and hasattr(w,'viewport'):
        w = w.viewport()
        # g.trace(w)
        sheet = "border: %spx solid %s" % (
            c.focus_border_width,c.focus_border_color)
        self.update_style_sheet(w,'border',sheet)

def remove_border(self,c,w):

    if c.use_focus_border and hasattr(w,'viewport'):
        w = w.viewport()
        # g.trace(w)
        sheet = "border: %spx solid white" % c.focus_border_width
        self.update_style_sheet(w,'border',sheet)
.. @+node:ekr.20111026115337.16528: *6* update_style_sheet (qtGui)
def update_style_sheet (self,w,key,value):
    
    trace = False and not g.unitTesting
    
    # Step one: update the dict.
    d = hasattr(w,'leo_styles_dict') and w.leo_styles_dict or {}
    d[key] = value
    aList = [d.get(key) for key in list(d.keys())]
    w.leo_styles_dict = d
    
    # Step two: update the stylesheet.
    s = '; '.join(aList)

    if trace: g.trace('\nold: %s\nnew: %s' % (
        str(w.styleSheet()),s))

    w.setStyleSheet(s)
.. @+node:ekr.20111010162047.15678: *5* Fix bug: Imports to @file should put @first/@last directives in root node
@nocolor-node

Especially:
    - Shebang line.
    - Python encoding line.
    
- @auto correctly works for all Python files.
.. @+node:ekr.20111221102703.10288: *6* @test imported shbang line
def setup(p):
    while p.hasChildren():
        p.firstChild().doDelete()
        
setup(p)
child = p.insertAsNthChild(0)
child.h = 'import'
child = p.firstChild()
assert child
assert child.h == 'import'
child.b = ''
c.selectPosition(child)

fn = g.os_path_finalize_join(g.app.loadDir,'..','test','at-auto-at-first-test.py')
assert g.os_path_exists(fn),fn

try:
    c.importCommands.createOutline(fn,parent=child.copy(),atAuto=False,ext='.py')
    s = '#!/usr/bin/env python\n# -*- coding: utf-8 -*-'
    root = child.firstChild()
    assert root
    assert root.h.startswith('@file'),root.h
    assert root.h.endswith('at-auto-at-first-test.py')
    assert root.b.startswith(s),'root.b:...\n\n%s' % (root.b)
finally:
    if not g.app.isExternalUnitTest:
        setup(p)
.. @+node:ekr.20111221102703.10289: *6* @@@nosent ../test/at-auto-at-first-test.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

def spam():
    pass
    
# This is the last line.
.. @+node:ekr.20110621085435.6531: *5* Fix bug: Improve the find panel and find menu
@nocolor-node

Recently, perhaps it was today, there was a remark made about Leo's
Find panel being confusing for newbies.  I have some sympathy for the
remarks.

1.  Looking at Leo's Edit:Find menu, there is no obvious way of doing
a search/replace operation.  I'll fix this soon after 4.9 final.

2.  The Find tab should make it clearer that it is a summary/status
tab only--not a way of actually initiating or continuing searches.
Perhaps using QLabel widgets instead of (disabled) QTextEdit widgets
for the find/change patterns would help a bit.
.. @+node:ekr.20110619173515.14895: *5* Fix bug: realpath in g.openWithFileName
@nocolor-node

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

g.openWithFileName needs

   fileName = g.os_path_realpath(fileName)

at the start of it, otherwise

g.openWithFileName("/home/tbrown/.leo/.todo.leo", c) will open another
instance of an already open "/mnt/usr1/usr1/home/tbrown/.leo/.todo.leo"
because it doesn't recognize them as being the same.

It would be nice if the original attempt to open
"/home/tbrown/.leo/.todo.leo" was not converted to
"/mnt/usr1/usr1/home/tbrown/.leo/.todo.leo", but I think the POSIX
spec. insists on giving CPython the real path form, so this is hard to
avoid.  You see this when selecting a file using a file dialog, in the
dialog you choose /home/me/myfile.txt, but the return
is /mnt/usr1/usr1/home/me/myfile.txt.

Anyway, let me know if and when I can push that to trunk, impacts seem
to be limited to (a) not failing to detect an already opened file in
g.openWithFileName, which is a bug, and (b) sometimes less attractive
apparent paths, which is annoying, but I think better than the bug.

(this of course trips up UNLs
if /mnt/usr1/usr1/home/tbrown/.leo/.todo.leo is already loaded and the
UNL is /home/tbrown/.leo/.todo.leo#Home-->Paint roof)
.. @+node:ekr.20120219152142.34262: *5* Fix bug: Revert moves the reverted tab to the right
.. @+node:ekr.20110530063322.18333: *5* Fix bug: scala not colored properly
.. @+node:ekr.20111125072438.10217: *5* Fix bug: shell interprets characters in at-mime headings
@nocolor-node

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

on linux, parenthesis or angled brackets in @mime headings prevent the file from
being opened, probably because they are interpreted specially by the shell, e.g.
they need to be escaped. Such characters should be escaped before passing the
filename to the shell.

.. @+node:ekr.20111018104244.15920: *5* Fix bug: Use QLocalSocket in LProtoClient.connect
http://groups.google.com/group/leo-editor/browse_thread/thread/fe9cddc0e7ac8199
.. @+node:ekr.20111014074810.15652: *5* Fix bug: viewrendered should work better when docutils does not exist
.. @+node:ekr.20110620083705.14901: *5* Fix or disable curses gui
leoBridge.py requires the nullGui.
.. @+node:ekr.20120313134250.9852: *5* Fix nav_qt bugs
See: https://bugs.launchpad.net/leo-editor/+bug/820019
Shift-Ctrl-F (quicksearch) Sometimes leaves focus in body


See: https://bugs.launchpad.net/leo-editor/+bug/869385
Chapters make the nav_qt.py plugin useless.
.. @+node:ekr.20111026091322.16495: *5* Fix quickmove bug
@nocolor-node

See: https://bugs.launchpad.net/leo-editor/+bug/875463
Enabling quickmove.py sends to error messages to console on every outline open 
.. @+node:ekr.20110620065017.14900: *4* Bugs/Can't/Won't fix
.. @+node:ekr.20111011062533.15693: *5* Make headline widgets scrollable, if possible
# It doesn't seem easy.
.. @+node:ekr.20110605121601.18422: *6* editLabelHelper (leoQtTree)
def editLabelHelper (self,item,selectAll=False,selection=None):

    '''Called by nativeTree.editLabel to do
    gui-specific stuff.'''

    trace = False and not g.unitTesting
    w = self.treeWidget
    w.setCurrentItem(item) # Must do this first.
    w.editItem(item)
        # Generates focus-in event that tree doesn't report.
    e = w.itemWidget(item,0) # A QLineEdit.

    if e:
        s = e.text() ; len_s = len(s)
        if s == 'newHeadline': selectAll=True
        if selection:
            i,j,ins = selection
            start,n = i,abs(i-j)
                # Not right for backward searches.
        elif selectAll: start,n,ins = 0,len_s,len_s
        else:           start,n,ins = len_s,0,len_s
        e.setObjectName('headline')
        e.setSelection(start,n)
        # e.setCursorPosition(ins) # Does not work.
        e.setFocus()
        wrapper = self.connectEditorWidget(e,item) # Hook up the widget.

    if trace: g.trace(e,wrapper)
    return e,wrapper # 2011/02/11
.. @+node:ekr.20110619173515.14896: *5* Fix bug: wrong modality level on autocompleter
Just noticed the autocompleter pop-up is modal globally, not just for
the Leo windows.  Probably should only block the Leo windows.
.. @+node:ekr.20120226183512.10199: *3* Features
Most important

* Rendering to tex,pdf, etc.
* Use c.db for marks and expansion bits.
* Restart vim project
* Bridges
.. @+node:ekr.20111109083738.9796: *4* First
.. @+node:ekr.20120226180525.10191: *5* Run unit tests interactively?
@nocolor-node

Getting value from Bret Victor's video

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

Thus, to associate code with unit tests, we need only create a
convention for associating source code with nodes.  But clones make
this trivial to do!

- @interactive-unit-tests
 - @itest
   - @test spam
   - (clone of) spam

This is all we would need to "declare" that @test spam should be run
whenever the spam node changes!

These are just first thoughts, made up as I am writing this post.  But
clearly, Leo can do more in this area.
.. @+node:ekr.20111028195854.16607: *5* Allow ruleset-specific colors
@nocolor-node

Like @color html::tags_markup_color = blue
.. @+node:ekr.20111027195140.16561: *5* Create session commands
# http://groups.google.com/group/leo-editor/browse_thread/thread/d5522e1075e746de

def get_session():
   """Return a list of UNLs for open tabs.
   """
   tabs = g.app.windowList
   UNLs = []
   for tab in tabs:
       current = tab.c.currentPosition()
       UNLs.append(current.get_UNL())
   return UNLs

def load_session(c, UNLs):
   """Open a tab for each item in UNLs.
   """
   for UNL in UNLs:
       fname, unl = UNL.split("#")
       c2 = g.openWithFileName(fname,old_c=c)
       if c2:
           for p in c2.all_positions():
               if p.get_UNL() == unl:
                   c2.setCurrentPosition(p)
                   c2.redraw()
                   break

def clear_session(c):
   """Close other tabs.
   The other session functions don't require parameters, use leotools.g
   I don't know how to get this filename from g, hence the ``c`` arg.
   """
   this_fname = c.fileName()
   tabs = g.app.windowList
   cmdrs = [tab.c for tab in tabs]
   for cmdr in cmdrs:
       if cmdr.fileName() !=this_fname:
           cmdr.close()

def session_save_snapshot():
   import json
   session = get_session()
   with file('leo.session', 'w') as f:
       json.dump(session, f)
       f.close()

def session_load_snapshot():
   import json
   with file('leo.session') as f:
       session = json.loads(f.read())
   return session
.. @+node:ekr.20111125072438.10219: *5* Open file dialogs starting with @path directory
@nocolor-node

File 'Open' or 'Import File' command to follow @path directive
http://groups.google.com/group/leo-editor/browse_thread/thread/7e508921c85d18bc

Suppose my focus node is under a @path directive, say @path E:
\Documents\Some Folder\

Is there a setting that would cause the 'File>Open' or 'File>Import
File' commands to begin the search dialog in the directory, E:
\Documents\Some Folder\ instead of the last directory used?

Would others find this a useful feature?
.. @+node:ekr.20111221114145.10217: *6* Found: runOpenFileDialog
# Important: many plugins use g.app.gui.openFileDialog.
.. @+node:ekr.20031218072017.2821: *7* c.open & helper
def open (self,event=None):

    '''Open a Leo window containing the contents of a .leo file.'''

    c = self
    << Set closeFlag if the only open window is empty >>
    table = [
        # 2010/10/09: Fix an interface blunder. Show all files by default.
        ("All files","*"),
        ("Leo files","*.leo"),
        ("Python files","*.py"),]

    fileName = ''.join(c.k.givenArgs) or g.app.gui.runOpenFileDialog(
        title = "Open",filetypes = table,defaultextension = ".leo")
    c.bringToFront()
    
    c.init_error_dialogs()

    ok = False
    if fileName:
        if fileName.endswith('.leo'):
            c2 = g.openWithFileName(fileName,old_c=c)
            if c2:
                g.chdir(fileName)
                g.setGlobalOpenDir(fileName)
            if c2 and closeFlag:
                g.app.destroyWindow(c.frame)
        elif c.looksLikeDerivedFile(fileName):
            # 2011/10/09: A smart open makes Leo lighter:
            # Create an @file node for files containing Leo sentinels.
            ok = c.importCommands.importDerivedFiles(parent=c.p,
                paths=[fileName],command='Open')
        else:
            # otherwise, create an @edit node.
            ok = c.createNodeFromExternalFile(fileName)
            
    c.raise_error_dialogs(kind='write')

    # openWithFileName sets focus if ok.
    if not ok:
        c.initialFocusHelper()
.. @+node:ekr.20031218072017.2822: *8* << Set closeFlag if the only open window is empty >>
@
If this is the only open window was opened when the app started, and
the window has never been written to or saved, then we will
automatically close that window if this open command completes
successfully.
@c

closeFlag = (
    c.frame.startupWindow and # The window was open on startup
    not c.changed and not c.frame.saved and # The window has never been changed
    g.app.numberOfUntitledWindows == 1) # Only one untitled window has ever been opened
.. @+node:ekr.20090212054250.9: *8* c.createNodeFromExternalFile
def createNodeFromExternalFile(self,fn):

    '''Read the file into a node.
    Return None, indicating that c.open should set focus.'''

    c = self

    s,e = g.readFileIntoString(fn)
    if s is None: return
    head,ext = g.os_path_splitext(fn)
    if ext.startswith('.'): ext = ext[1:]
    language = g.app.extension_dict.get(ext)
    if language:
        prefix = '@color\n@language %s\n\n' % language
    else:
        prefix = '@killcolor\n\n'
    p2 = c.insertHeadline(op_name='Open File', as_child=False)
    p2.h = '@edit %s' % fn # g.shortFileName(fn)
    p2.b = prefix + s
    w = c.frame.body.bodyCtrl
    if w: w.setInsertPoint(0)
    c.redraw()
    c.recolor()
.. @+node:ekr.20111009162434.7206: *7* Regularized slashes in @edit/@file
@nocolor-node

Examples:
    
    @edit C:/leo.repo/ipython-0.12/IPython/frontend/qt/console/pygments_highlighter.py
        # The filename comes from g.app.gui.runOpenFileDialog
    
    @file C:\leo.repo\ipython-0.12\IPython\frontend\qt\console\pygments_highlighter.py
        # Created by import.
        
The problem was the call to g.os_path_normpath in importDerived files:
    it converts slashes to backslashes on windows (!!)
.. @+node:ekr.20110530124245.18245: *7* c.importAnyFile & helper
def importAnyFile (self,event=None):

    '''Import one or more files.'''

    c = self ; ic = c.importCommands

    types = [
        ("All files","*"),
        ("C/C++ files","*.c"),
        ("C/C++ files","*.cpp"),
        ("C/C++ files","*.h"),
        ("C/C++ files","*.hpp"),
        ("Java files","*.java"),
        ("Lua files", "*.lua"),
        ("Pascal files","*.pas"),
        ("Python files","*.py") ]

    names = g.app.gui.runOpenFileDialog(
        title="Import File",
        filetypes=types,
        defaultextension=".py",
        multiple=True)
    c.bringToFront()
    
    if names:
        g.chdir(names[0])
    else:
        names = []

    if not names:
        if g.unitTesting:
            # a kludge for unit testing.
            c.init_error_dialogs()
            c.raise_error_dialogs(kind='read')
        return
        
    
    
    # New in Leo 4.9: choose the type of import based on the extension.
    
    c.init_error_dialogs()
    
    derived = [z for z in names if c.looksLikeDerivedFile(z)]
    others = [ z for z in names if not z in derived]
    
    if derived:
        ic.importDerivedFiles(parent=c.p,paths=derived)
    
    for fn in others:
        junk,ext = g.os_path_splitext(fn)
        if ext.startswith('.'): ext = ext[1:]
    
        if ext in ('cw','cweb'):
            ic.importWebCommand([fn],"cweb")
        elif ext in ('nw','noweb'):
            ic.importWebCommand([fn],"noweb")
        elif ext == 'txt':
            ic.importFlattenedOutline([fn])
        else:
            ic.importFilesCommand([fn],"@file")
            
        # No longer supported.
        # c.importCommands.importFilesCommand (names,"@root")
        
    c.raise_error_dialogs(kind='read')
        
# Compatibility
importAtFile = importAnyFile
importAtRoot = importAnyFile
importCWEBFiles = importAnyFile
importDerivedFile = importAnyFile
importFlattenedOutline = importAnyFile
importNowebFiles = importAnyFile
.. @+node:ekr.20110530124245.18248: *8* c.looksLikeDerivedFile
def looksLikeDerivedFile (self,fn):
    
    '''Return True if fn names a file that looks like an
    external file written by Leo.'''
    
    c = self
    
    try:
        f = open(fn,'r')
    except IOError:
        return False
        
    s = f.read()
    f.close()
    
    val = s.find('@+leo-ver=') > -1
    # g.trace(val,fn)
    return val
.. @+node:ekr.20031218072017.2839: *7* c.readOutlineOnly
def readOutlineOnly (self,event=None):

    '''Open a Leo outline from a .leo file, but do not read any derived files.'''

    c = self
    c.endEditing()

    fileName = g.app.gui.runOpenFileDialog(
        title="Read Outline Only",
        filetypes=[("Leo files", "*.leo"), ("All files", "*")],
        defaultextension=".leo")

    if not fileName:
        return

    try:
        theFile = open(fileName,'r')
        g.chdir(fileName)
        c = g.app.newCommander(fileName)
        frame = c.frame
        frame.deiconify()
        frame.lift()
        c.fileCommands.readOutlineOnly(theFile,fileName) # closes file.
    except:
        g.es("can not open:",fileName)
.. @+node:ekr.20070915134101: *7* c.readFileIntoNode
def readFileIntoNode (self,event=None):

    '''Read a file into a single node.'''

    c = self ; undoType = 'Read File Into Node'
    c.endEditing()

    filetypes = [("All files", "*"),("Python files","*.py"),("Leo files", "*.leo"),]
    fileName = g.app.gui.runOpenFileDialog(
        title="Read File Into Node",filetypes=filetypes,defaultextension=None)
    if not fileName:return
    s,e = g.readFileIntoString(fileName)
    if s is None: return

    g.chdir(fileName)
    s = '@nocolor\n' + s
    w = c.frame.body.bodyCtrl
    p = c.insertHeadline(op_name=undoType)
    p.setHeadString('@read-file-into-node ' + fileName)
    p.setBodyString(s)
    w.setAllText(s)
    c.redraw(p)
.. @+node:ekr.20031218072017.2859: *7* c.removeSentinels
def removeSentinels (self,event=None):

    '''Import one or more files, removing any sentinels.'''

    c = self

    types = [
        ("All files","*"),
        ("C/C++ files","*.c"),
        ("C/C++ files","*.cpp"),
        ("C/C++ files","*.h"),
        ("C/C++ files","*.hpp"),
        ("Java files","*.java"),
        ("Lua files", "*.lua"),
        ("Pascal files","*.pas"),
        ("Python files","*.py") ]

    names = g.app.gui.runOpenFileDialog(
        title="Remove Sentinels",
        filetypes=types,
        defaultextension=".py",
        multiple=True)
    c.bringToFront()

    if names:
        g.chdir(names[0])
        c.importCommands.removeSentinelsCommand (names)
.. @+node:ekr.20050920084036.20: *7* readAbbreviations & helper
def readAbbreviations (self,event=None):

    '''Read abbreviations from a file.'''

    fileName = g.app.gui.runOpenFileDialog(
        title = 'Open Abbreviation File',
        filetypes = [("Text","*.txt"), ("All files","*")],
        defaultextension = ".txt")

    if fileName:
        self.readAbbreviationsFromFile(fileName)
.. @+node:ekr.20100901080826.6156: *8* readAbbreviationsFromFile
def readAbbreviationsFromFile(self,fileName):

    k = self.c.k

    try:
        f = open(fileName)
        for s in f:
            self.addAbbrevHelper(s,'file')
        f.close()
        k.abbrevOn = True
        g.es("Abbreviations on")
        # self.listAbbrevs()
    except IOError:
        g.es('can not open',fileName)
.. @+node:ekr.20071114081313.1: *7* insertIcon
def insertIcon (self,event=None):
    
    '''Prompt for an icon, and insert it into the node's icon list.'''

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

    iconDir = c.os_path_finalize_join(g.app.loadDir,"..","Icons")
    os.chdir(iconDir)

    paths = g.app.gui.runOpenFileDialog(
        title='Get Icons',
        filetypes=[('All files','*'),('Gif','*.gif'), ('Bitmap','*.bmp'),('Icon','*.ico'),],
        defaultextension=None,
        multiple=True)

    if not paths: return

    aList = [] ; xoffset = 2
    for path in paths:
        xoffset = self.appendImageDictToList(aList,iconDir,path,xoffset)

    aList2 = self.getIconList(p)
    aList2.extend(aList)
    self.setIconList(p, aList2)
    c.setChanged(True)
    c.redraw_after_icons_changed()
.. @+node:ekr.20070920104110: *7* compareLeoFiles
def compareLeoFiles (self,event):
    
    '''Compare two .leo files.'''

    c = c1 = self.c ; w = c.frame.body.bodyCtrl

    # Prompt for the file to be compared with the present outline.
    filetypes = [("Leo files", "*.leo"),("All files", "*"),]
    fileName = g.app.gui.runOpenFileDialog(
        title="Compare .leo Files",filetypes=filetypes,defaultextension='.leo')
    if not fileName: return

    # Read the file into the hidden commander.
    c2 = self.createHiddenCommander(fileName)
    if not c2: return

    # Compute the inserted, deleted and changed dicts.
    d1 = self.createFileDict(c1)
    d2 = self.createFileDict(c2)  
    inserted, deleted, changed = self.computeChangeDicts(d1,d2)
    self.dumpCompareNodes(fileName,c1.mFileName,inserted,deleted,changed)

    # Create clones of all inserted, deleted and changed dicts.
    self.createAllCompareClones(inserted,deleted,changed)
    c2.frame.destroySelf()
    g.app.gui.set_focus(c,w)


.. @+node:ekr.20070921072608: *8* computeChangeDicts
def computeChangeDicts (self,d1,d2):

    '''Compute inserted, deleted, changed dictionaries.'''

    inserted = {}
    for key in d2:
        if not d1.get(key):
            inserted[key] = d2.get(key)

    deleted = {}
    for key in d1:
        if not d2.get(key):
            deleted[key] = d1.get(key)

    changed = {}
    for key in d1:
        if d2.get(key):
            p1 = d1.get(key)
            p2 = d2.get(key)
            if p1.h != p2.h or p1.b != p2.b:
                changed[key] = p1

    return inserted, deleted, changed
.. @+node:ekr.20070921072910: *8* createAllCompareClones & helper
def createAllCompareClones(self,inserted,deleted,changed):

    c = self.c # Always use the visible commander
    # Create parent node at the start of the outline.
    u = c.undoer ; undoType = 'Compare .leo Files'
    u.beforeChangeGroup(c.p,undoType)
    undoData = u.beforeInsertNode(c.p)
    parent = c.p.insertAfter()
    parent.setHeadString(undoType)
    u.afterInsertNode(parent,undoType,undoData,dirtyVnodeList=[])
    for d,kind in (
        (deleted,'deleted'),(inserted,'inserted'),(changed,'changed')
    ):
        self.createCompareClones(d,kind,parent)
    c.selectPosition(parent)
    u.afterChangeGroup(parent,undoType,reportFlag=True) 
    c.redraw()
.. @+node:ekr.20070921074410: *9* createCompareClones
def createCompareClones (self,d,kind,parent):

    c = self.c # Always use the visible commander.

    if d:
        parent = parent.insertAsLastChild()
        parent.setHeadString(kind)

        for key in d:
            p = d.get(key)
            clone = p.clone()
            clone.moveToLastChildOf(parent)
.. @+node:ekr.20070921070101: *8* createHiddenCommander (editFileCommandsClass)
def createHiddenCommander(self,fn):

    '''Read the file into a hidden commander (Similar to g.openWithFileName).'''
    
    import leo.core.leoCommands as leoCommands
    lm = g.app.loadManager

    c2 = leoCommands.Commands(fn,gui=g.app.nullGui)
    theFile = lm.openLeoOrZipFile(fn)

    if theFile:
        c2.fileCommands.openLeoFile(theFile,fn,
            readAtFileNodesFlag=True,silent=True)
        return c2
    else:
        return None
.. @+node:ekr.20070921070101.1: *8* createFileDict
def createFileDict (self,c):

    '''Create a dictionary of all relevant positions in commander c.'''

    d = {}
    for p in c.all_positions():
        try:
            # fileIndices for pre-4.x versions of .leo files have a different format.
            i,j,k = p.v.fileIndex
            d[str(i),str(j),str(k)] = p.copy()
        except Exception:
            pass
    return d
.. @+node:ekr.20070921072608.1: *8* dumpCompareNodes
def dumpCompareNodes (self,fileName1,fileName2,inserted,deleted,changed):

    for d,kind in (
        (inserted,'inserted (only in %s)' % (fileName1)),
        (deleted, 'deleted  (only in %s)' % (fileName2)),
        (changed, 'changed'),
    ):
        g.pr('\n',kind)
        for key in d:
            p = d.get(key)
            if g.isPython3:
                g.pr('%-32s %s' % (key,p.h))
            else:
                g.pr('%-32s %s' % (key,g.toEncodedString(p.h,'ascii')))
.. @+node:ekr.20050920084036.166: *7* getReadableTextFile
def getReadableTextFile (self):

    fn = g.app.gui.runOpenFileDialog(
        title = 'Open Text File',
        filetypes = [("Text","*.txt"), ("All files","*")],
        defaultextension = ".txt")

    return fn
.. @+node:ekr.20031218072017.3731: *7* app.gui file dialogs
def runOpenFileDialog(self,title,filetypes,defaultextension,multiple=False):

    """Create and run an open file dialog ."""

    self.oops()

def runSaveFileDialog(self,initialfile,title,filetypes,defaultextension):

    """Create and run a save file dialog ."""

    self.oops()
.. @+node:ekr.20031218072017.3744: *7* dialogs (nullGui)
def runAboutLeoDialog(self,c,version,theCopyright,url,email):
    return self.simulateDialog("aboutLeoDialog")

def runAskLeoIDDialog(self):
    return self.simulateDialog("leoIDDialog")

def runAskOkDialog(self,c,title,message=None,text="Ok"):
    return self.simulateDialog("okDialog","Ok")

def runAskOkCancelNumberDialog(self,c,title,message):
    return self.simulateDialog("numberDialog",-1)

def runAskOkCancelStringDialog(self,c,title,message):
    return self.simulateDialog("stringDialog",'')

def runCompareDialog(self,c):
    return self.simulateDialog("compareDialog",'')

def runOpenFileDialog(self,title,filetypes,defaultextension,multiple=False):
    return self.simulateDialog("openFileDialog")

def runSaveFileDialog(self,initialfile,title,filetypes,defaultextension):
    return self.simulateDialog("saveFileDialog")

def runAskYesNoDialog(self,c,title,message=None):
    return self.simulateDialog("yesNoDialog","no")

def runAskYesNoCancelDialog(self,c,title,
    message=None,yesMessage="Yes",noMessage="No",defaultButton="Yes"):
    return self.simulateDialog("yesNoCancelDialog","cancel")
.. @+node:ekr.20110605121601.18500: *7* runOpenFileDialog (qtGui)
def runOpenFileDialog(self,title,filetypes,defaultextension='',multiple=False,startpath=None):

    """Create and run an Qt open file dialog ."""
    
    if g.unitTesting:
        return ''
    else:
        if startpath is None:
            startpath = os.curdir
            
        parent = None
        filter = self.makeFilter(filetypes)
    
        if multiple:
            lst = QtGui.QFileDialog.getOpenFileNames(parent,title,startpath,filter)
            return [g.u(s) for s in lst]
        else:
            s = QtGui.QFileDialog.getOpenFileName(parent,title,startpath,filter)
            return g.u(s)
.. @+node:ekr.20111125072438.10216: *5* Regularize slashes and back-slashes
@nocolor-node

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

v4.9.1 build 4669
WinXP

If I 'Open' a file, I get the following node header:

@edit E:/Documents/index.html

If I 'Import' the same file, I get the following:

@file E:\Documents\index.html

Just wondering why the difference between

E:/ and E:\

This is probably trivial and of no consequence, but I thought it
curious. 
.. @+node:ekr.20111108081936.12524: *6* Create unit tests illustrating path ops
@nocolor-node

viewrendered - file not found
https://groups.google.com/forum/#!topic/leo-editor/kotyIs6_G3w

This would be part of a redesign of Leo's path operations.
.. @+node:ekr.20031218072017.2145: *7* os.path wrappers (leoGlobals.py)
@ Note: all these methods return Unicode strings. It is up to the user to
convert to an encoded string as needed, say when opening a file.
.. @+node:ekr.20031218072017.2146: *8* g.os_path_abspath
def os_path_abspath(path):

    """Convert a path to an absolute path."""

    path = g.toUnicodeFileEncoding(path)

    path = os.path.abspath(path)

    path = g.toUnicodeFileEncoding(path)

    return path
.. @+node:ekr.20031218072017.2147: *8* g.os_path_basename
def os_path_basename(path):

    """Return the second half of the pair returned by split(path)."""

    path = g.toUnicodeFileEncoding(path)

    path = os.path.basename(path)

    path = g.toUnicodeFileEncoding(path)

    return path
.. @+node:ekr.20031218072017.2148: *8* g.os_path_dirname
def os_path_dirname(path):

    """Return the first half of the pair returned by split(path)."""

    path = g.toUnicodeFileEncoding(path)

    path = os.path.dirname(path)

    path = g.toUnicodeFileEncoding(path)

    return path
.. @+node:ekr.20031218072017.2149: *8* g.os_path_exists
def os_path_exists(path):

    """Return True if path exists."""

    path = g.toUnicodeFileEncoding(path)

    return os.path.exists(path)
.. @+node:ekr.20080922124033.6: *8* g.os_path_expandExpression
def os_path_expandExpression (s,**keys):

    '''Expand {{anExpression}} in c's context.'''

    trace = False
    
    s1 = s
    c = keys.get('c')
    if not c:
        g.trace('can not happen: no c',g.callers())
        return s

    if not s:
        if trace: g.trace('no s')
        return ''

    i = s.find('{{')
    j = s.find('}}')
    if -1 < i < j:
        exp = s[i+2:j].strip()
        if exp:
            try:
                import os
                import sys
                p = c.p
                d = {'c':c,'g':g,'p':p,'os':os,'sys':sys,}
                val = eval(exp,d)
                s = s[:i] + str(val) + s[j+2:]
                if trace: g.trace('returns',s)
            except Exception:
                g.trace(g.callers())
                g.es_exception(full=True, c=c, color='red')

    return s
.. @+node:ekr.20080921060401.13: *8* g.os_path_expanduser
def os_path_expanduser(path):

    """wrap os.path.expanduser"""

    path = g.toUnicodeFileEncoding(path)

    result = os.path.normpath(os.path.expanduser(path))

    return result
.. @+node:ekr.20080921060401.14: *8* g.os_path_finalize & os_path_finalize_join
def os_path_finalize (path,**keys):

    '''
    Expand '~', then return os.path.normpath, os.path.abspath of the path.

    There is no corresponding os.path method'''

    c = keys.get('c')

    if c: path = g.os_path_expandExpression(path,**keys)

    path = g.os_path_expanduser(path)
    path = os.path.abspath(path)
    path = os.path.normpath(path)
    return path

def os_path_finalize_join (*args,**keys):

    '''Do os.path.join(*args), then finalize the result.'''

    c = keys.get('c')

    if c:
        args = [g.os_path_expandExpression(z,**keys)
            for z in args if z]

    return os.path.normpath(os.path.abspath(
        g.os_path_join(*args,**keys))) # Handles expanduser
.. @+node:ekr.20031218072017.2150: *8* g.os_path_getmtime
def os_path_getmtime(path):

    """Return the modification time of path."""

    path = g.toUnicodeFileEncoding(path)

    return os.path.getmtime(path)
.. @+node:ekr.20080729142651.2: *8* g.os_path_getsize
def os_path_getsize (path):

    '''Return the size of path.'''

    path = g.toUnicodeFileEncoding(path)

    return os.path.getsize(path)
.. @+node:ekr.20031218072017.2151: *8* g.os_path_isabs
def os_path_isabs(path):

    """Return True if path is an absolute path."""

    path = g.toUnicodeFileEncoding(path)

    return os.path.isabs(path)
.. @+node:ekr.20031218072017.2152: *8* g.os_path_isdir
def os_path_isdir(path):

    """Return True if the path is a directory."""

    path = g.toUnicodeFileEncoding(path)

    return os.path.isdir(path)
.. @+node:ekr.20031218072017.2153: *8* g.os_path_isfile
def os_path_isfile(path):

    """Return True if path is a file."""

    path = g.toUnicodeFileEncoding(path)

    return os.path.isfile(path)
.. @+node:ekr.20031218072017.2154: *8* g.os_path_join
def os_path_join(*args,**keys):

    trace = False and not g.unitTesting
    c = keys.get('c')

    uargs = [g.toUnicodeFileEncoding(arg) for arg in args]

    if trace: g.trace('1',uargs)

    # Note:  This is exactly the same convention as used by getBaseDirectory.
    if uargs and uargs[0] == '!!':
        uargs[0] = g.app.loadDir
    elif uargs and uargs[0] == '.':
        c = keys.get('c')
        if c and c.openDirectory:
            uargs[0] = c.openDirectory
            # g.trace(c.openDirectory)

    uargs = [g.os_path_expanduser(z) for z in uargs if z]

    if trace: g.trace('2',uargs)

    path = os.path.join(*uargs)

    if trace: g.trace('3',path)

    # May not be needed on some Pythons.
    path = g.toUnicodeFileEncoding(path)
    return path
.. @+node:ekr.20031218072017.2156: *8* g.os_path_normcase
def os_path_normcase(path):

    """Normalize the path's case."""

    path = g.toUnicodeFileEncoding(path)

    path = os.path.normcase(path)

    path = g.toUnicodeFileEncoding(path)

    return path
.. @+node:ekr.20031218072017.2157: *8* g.os_path_normpath
def os_path_normpath(path):

    """Normalize the path."""

    path = g.toUnicodeFileEncoding(path)

    path = os.path.normpath(path)

    path = g.toUnicodeFileEncoding(path)

    return path
.. @+node:ekr.20080605064555.2: *8* g.os_path_realpath
def os_path_realpath(path):


    path = g.toUnicodeFileEncoding(path)

    path = os.path.realpath(path)

    path = g.toUnicodeFileEncoding(path)

    return path
.. @+node:ekr.20031218072017.2158: *8* g.os_path_split
def os_path_split(path):

    path = g.toUnicodeFileEncoding(path)

    head,tail = os.path.split(path)

    head = g.toUnicodeFileEncoding(head)
    tail = g.toUnicodeFileEncoding(tail)

    return head,tail
.. @+node:ekr.20031218072017.2159: *8* g.os_path_splitext
def os_path_splitext(path):

    path = g.toUnicodeFileEncoding(path)

    head,tail = os.path.splitext(path)

    head = g.toUnicodeFileEncoding(head)
    tail = g.toUnicodeFileEncoding(tail)

    return head,tail
.. @+node:ekr.20090829140232.6036: *8* g.os_startfile
def os_startfile(fname):
    
    if g.unitTesting:
        g.app.unitTestDict['os_startfile']=fname
        return
        
    if fname.find('"') > -1:
        quoted_fname = "'%s'" % fname
    else:
        quoted_fname = '"%s"' % fname

    if sys.platform.startswith('win'):
        os.startfile(quoted_fname)
            # Exists only on Windows.
    elif sys.platform == 'darwin':
        # From Marc-Antoine Parent.
        try:
            subprocess.call(['open', quoted_fname])
        except OSError:
            pass # There may be a spurious "Interrupted system call"
        except ImportError:
            os.system('open %s' % (quoted_fname))
    else:
        # os.system('xdg-open "%s"' % (fname))
        try:
            val = subprocess.call('xdg-open %s' % (quoted_fname),shell=True)
            if val < 0:
                g.es_print('xdg-open %s failed' % (fname))
        except Exception:
            g.es_print('error opening %s' % fname)
            g.es_exception()
.. @+node:ekr.20031218072017.2160: *8* g.toUnicodeFileEncoding
def toUnicodeFileEncoding(path):

    if path: path = path.replace('\\', os.sep)

    # Yes, this is correct.  All os_path_x functions return Unicode strings.
    return g.toUnicode(path)
.. @+node:ekr.20111108081936.9750: *7* @test path computations
fj = g.os_path_finalize_join
f  = g.os_path_finalize
eu = g.os_path_expanduser

if g.app.isExternalUnitTest:
    loadDir = fj(g.app.loadDir,'..','test')
else:
    loadDir = g.app.loadDir

table = (
    (fj,'@@file fj-test-1',fj(loadDir,'rel-path')),
)

for func,h,expected in table:
    p = g.findNodeAnywhere(c,h)
    assert p,'not found: "%s"' % (h)
    assert p.h.startswith('@@')
    p.h = p.h[1:] # Remove the first @ sign.
    
    try:
        d = c.scanAllDirectives(p)
        result = d.get('path')
        assert result == expected,'expected "%s", got "%s"' % (
            expected,result)
    finally:
        p.h = '@' + p.h
        c.redraw()
.. @+node:ekr.20111108081936.9768: *8* @path rel-path
.. @+node:ekr.20111108081936.9751: *9* @@file fj-test-1
.. @+node:ekr.20031218072017.3213: *6* createImportParent (importCommands)
def createImportParent (self,current,files):
    
    '''Create a parent node for nodes with a common prefix: x.h & x.cpp.'''

    name0,name1 = files
    prefix0, junk = g.os_path_splitext(name0)
    prefix1, junk = g.os_path_splitext(name1)
    
    if prefix0 and prefix0 == prefix1:
        current = current.insertAsLastChild()
        name,junk = g.os_path_splitext(prefix1)
        name = name.replace('\\','/') # 2011/11/25
        current.initHeadString(name)

    return current
.. @+node:ekr.20031218072017.3210: *6* ic.createOutline
def createOutline (self,fileName,parent,
    atAuto=False,atShadow=False,s=None,ext=None):

    c = self.c ; u = c.undoer ; s1 = s
    w = c.frame.body
    at = c.atFileCommands

    self.default_directory = g.setDefaultDirectory(c,parent,importing=False)
    fileName = c.os_path_finalize_join(self.default_directory,fileName)
    fileName = fileName.replace('\\','/') # 2011/11/25
    junk,self.fileName = g.os_path_split(fileName)
    self.methodName,self.fileType = g.os_path_splitext(self.fileName)
    self.setEncoding(p=parent,atAuto=atAuto)
    if not ext: ext = self.fileType
    ext = ext.lower()
    if not s:
        if atShadow: kind = '@shadow '
        elif atAuto: kind = '@auto '
        else: kind = ''
        s,e = g.readFileIntoString(fileName,encoding=self.encoding,kind=kind)
        if s is None: return None
        if e: self.encoding = e

    # Create the top-level headline.
    if atAuto:
        p = parent.copy()
        p.setBodyString('')
    else:
        undoData = u.beforeInsertNode(parent)
        p = parent.insertAsLastChild()

        if self.treeType == "@file":
            p.initHeadString("@file " + fileName)
        elif self.treeType is None:
            # 2010/09/29: by convention, we use the short file name.
            p.initHeadString(g.shortFileName(fileName))
        else:
            p.initHeadString(fileName)
        u.afterInsertNode(p,'Import',undoData)

    if self.treeType == '@root': # 2010/09/29.
        self.rootLine = "@root-code "+self.fileName+'\n'
    else:
        self.rootLine = ''

    if p.isAtAutoRstNode(): # @auto-rst is independent of file extension.
        func = self.scanRstText
    else:
        func = self.importDispatchDict.get(ext)

    if func and not c.config.getBool('suppress_import_parsing',default=False):
        s = s.replace('\r','')
        func(s,p,atAuto=atAuto)
    else:
        # Just copy the file to the parent node.
        s = s.replace('\r','')
        self.scanUnknownFileType(s,p,ext,atAuto=atAuto)

    if atAuto:
        # Fix bug 488894: unsettling dialog when saving Leo file
        # Fix bug 889175: Remember the full fileName.
        at.rememberReadPath(fileName,p)

    p.contract()
    w.setInsertPoint(0)
    w.seeInsertPoint()
    return p
.. @+node:ekr.20081001062423.9: *6* g.setDefaultDirectory & helper
def setDefaultDirectory(c,p,importing=False):

    ''' Return a default directory by scanning @path directives.'''

    name = p.anyAtFileNodeName()
    if name:
        # An absolute path overrides everything.
        d = g.os_path_dirname(name)
        if d and g.os_path_isabs(d):
            return d

    aList = g.get_directives_dict_list(p)
    path = c.scanAtPathDirectives(aList)
        # Returns g.getBaseDirectory(c) by default.
        # However, g.getBaseDirectory can return ''
    if path:
        path = g.os_path_finalize(path)
    else:
        g.checkOpenDirectory(c)
        for d in (c.openDirectory,g.getBaseDirectory(c)):
            # Errors may result in relative or invalid path.
            if d and g.os_path_isabs(d):
                path = d
                break
        else:
            path = ''

    if not importing and not path:
        # This should never happen, but is not serious if it does.
        g.warning("No absolute directory specified anywhere.")

    return path
.. @+node:ekr.20101022124309.6132: *7* g.checkOpenDirectory
def checkOpenDirectory (c):

    if c.openDirectory != c.frame.openDirectory:
        g.error(
            'c.openDirectory != c.frame.openDirectory\n'
            'c.openDirectory: %s\n'
            'c.frame.openDirectory: %s' % (
                c.openDirectory,c.frame.openDirectory))

    if not g.os_path_isabs(c.openDirectory):
        g.error ('relative c.openDirectory: %s' % (
            c.openDirectory))
.. @+node:ekr.20080921060401.14: *6* g.os_path_finalize & os_path_finalize_join
def os_path_finalize (path,**keys):

    '''
    Expand '~', then return os.path.normpath, os.path.abspath of the path.

    There is no corresponding os.path method'''

    c = keys.get('c')

    if c: path = g.os_path_expandExpression(path,**keys)

    path = g.os_path_expanduser(path)
    path = os.path.abspath(path)
    path = os.path.normpath(path)
    return path

def os_path_finalize_join (*args,**keys):

    '''Do os.path.join(*args), then finalize the result.'''

    c = keys.get('c')

    if c:
        args = [g.os_path_expandExpression(z,**keys)
            for z in args if z]

    return os.path.normpath(os.path.abspath(
        g.os_path_join(*args,**keys))) # Handles expanduser
.. @+node:ekr.20111019080436.15848: *4* Easy
.. @+node:ekr.20111019104425.15869: *5* Convert @command nodes to official commands
@nocolor-node

May require command-related settings.

Example:
    @path create-at-auto-nodes-path=C:\apps\pygments\pygments
    @string create-at-auto-nodes-types=.py,
    @bool create-at-auto-nodes-is-recursive=True
.. @+node:ekr.20111017102409.15875: *5* Create print-buttons command
@nocolor-node

- Create print-buttons command, showing source of all @command and @button nodes.
.. @+node:ekr.20111010122531.15569: *5* print-bindings/commands/settings create nodes
@language rest

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

A reminder, the opposite of "light" is "heavy", not "dark" :-)

Leo's print-settings, print-commands and print-bindings commands
create too much text.

Suppose they created outline nodes instead, replacing existing nodes
if they exist.  Something like this:

- Reference (Anywhere you like)
 - @print-settings
    etc.
 - @print-bindings
   etc
 - @print commands
   etc

Doh!  This uses Leo's power.  The subnodes can be as voluminous as
desired, and there can be organizer nodes in each case.  The actual
tree could be specified as in @menus.

Etc., etc.  This could moot the need for separate apropos commands.
Conversely, apropos commands could create their own trees, or
subtrees.

This could be the tip of an iceberg.

The more I think about the light/heavy distinction, the more I think
it is getting close to what makes Leo special.  For example, clones
(and nodes, for that matter) drastically lighten the apparent
complexity of programs or data.
.. @+node:ekr.20111010093113.15547: *5* Add support for ! in minibuffer
.. @+node:ekr.20061031131434.145: *6* k.Master event handlers
.. @+node:ekr.20061031131434.105: *7* k.masterCommand & helpers
def masterCommand (self,commandName=None,event=None,func=None,stroke=None):

    '''This is the central dispatching method.
    All commands and keystrokes pass through here.'''

    k = self ; c = k.c ; gui = g.app.gui
    trace = (False or g.trace_masterCommand) and not g.unitTesting
    verbose = True
    traceGC = False
    if traceGC: g.printNewObjects('masterCom 1')

    if event: c.check_event(event)

    c.setLog()
    c.startRedrawCount = c.frame.tree.redrawCount
    k.stroke = stroke # Set this global for general use.

    char = ch = event and event.char or ''
    w = event and event.w
    
    # 2011/10/28: compute func if not given.
    if commandName and not func:
        func = c.commandsDict.get(commandName)
        
    # Important: it is *not* an error for func to be None.
    k.func = func
    commandName = commandName or func and func.__name__ or '<no function>'
    k.funcReturn = None # For unit testing.
    << define specialKeysyms >>
    special = char in specialKeysyms
    interesting = func is not None
    inserted = not special

    if trace: # Useful.
        g.trace('stroke: %s ch: %s func: %s' % (
            stroke,repr(ch),func and func.__name__))

    if inserted:
        k.setLossage(ch,stroke)

    # We *must not* interfere with the global state in the macro class.
    if c.macroCommands.recordingMacro:
        c.macroCommands.startRecordingMacro(event)
        # 2011/06/06: Show the key, if possible.
        # return

    if k.abortAllModesKey and stroke == k.abortAllModesKey: # 'Control-g'
        k.keyboardQuit()
        k.endCommand(commandName)
        return

    if special: # Don't pass these on.
        return

    # if k.regx.iter:
        # try:
            # k.regXKey = char
            # k.regx.iter.next() # EKR: next() may throw StopIteration.
        # except StopIteration:
            # pass
        # return

    if k.abbrevOn:
        expanded = c.abbrevCommands.expandAbbrev(event,stroke)
        if expanded: return

    if func: # Func is an argument.
        if commandName == 'propagate-key-event':
            # Do *nothing* with the event.
            return k.propagateKeyEvent(event)
        elif commandName.startswith('specialCallback'):
            # The callback function will call c.doCommand
            if trace: g.trace('calling specialCallback for',commandName)
            # if commandName != 'repeat-complex-command': # 2010/01/11
                # k.mb_history.insert(0,commandName)
            val = func(event)
            # k.simulateCommand uses k.funcReturn.
            k.funcReturn = k.funcReturn or val # For unit tests.
        else:
            # Call c.doCommand directly
            if trace: g.trace('calling command directly',commandName)
            c.doCommand(func,commandName,event=event)
        if c.exists:
            k.endCommand(commandName)
            c.frame.updateStatusLine()
        if traceGC: g.printNewObjects('masterCom 2')
        return # (for Tk) 'break'
    elif k.inState():
        return # (for Tk) 'break' #Ignore unbound keys in a state.
    else:
        if traceGC: g.printNewObjects('masterCom 3')
        val = k.handleDefaultChar(event,stroke)
        if c.exists:
            c.frame.updateStatusLine()
        if traceGC: g.printNewObjects('masterCom 4')
        return val
.. @+node:ekr.20061031131434.106: *8* << define specialKeysyms >>
specialKeysyms = (
    'Alt_L','Alt_R',
    'Meta_L','Meta_R', # Meta support.
    'Caps_Lock','Control_L','Control_R',
    'Num_Lock',
    'Shift_L','Shift_R',
)
.. @+node:ekr.20061031131434.110: *8* k.handleDefaultChar
def handleDefaultChar(self,event,stroke):

    k = self ; c = k.c
    w = event and event.widget
    name = c.widget_name(w)
    trace = False and not g.unitTesting
    verbose = False

    if trace and verbose:
        g.trace('widget_name',name,'stroke',stroke,'enable alt-ctrl',self.enable_alt_ctrl_bindings)

    if (stroke and
        not stroke.startswith('Alt+Ctrl') and
        # not k.enable_alt_ctrl_bindings and # Old code: this isn't an alt-ctrl key!
        k.ignore_unbound_non_ascii_keys and # Bug fix: 2011/11/23
        (stroke.find('Ctrl') > -1 or stroke.find('Alt') > -1)
    ):
        if trace: g.trace('*** ignoring unbound ctrl/alt key:',stroke)
        g.app.unitTestDict['handleUnboundChar-ignore-alt-or-ctrl'] = True
        return # (for Tk) 'break'

    if name.startswith('body'):
        action = k.unboundKeyAction
        if action in ('insert','overwrite'):
            c.editCommands.selfInsertCommand(event,action=action)
        else: # Ignore the key
            if trace: g.trace('ignoring',stroke)
        return # (for Tk) 'break'
    elif name.startswith('head'):
        c.frame.tree.onHeadlineKey(event)
        return # (for Tk) 'break'
    elif name.startswith('canvas'):
        if not stroke: # Not exactly right, but it seems to be good enough.
            c.onCanvasKey(event) # New in Leo 4.4.2
        return # (for Tk) 'break'
    elif name.startswith('log'):
        # Bug fix: 2011/11/21: Because of universal bindings
        # we may not be able to insert anything into w.
        import leo.core.leoFrame as leoFrame
        if issubclass(w.__class__,leoFrame.HighLevelInterface):
            i = w.logCtrl.getInsertPoint()
            if not stroke:
                stroke = event and event.stroke
            if stroke:
                s = stroke.toGuiChar()
                w.logCtrl.insert(i,s)
        else:
            if trace: g.trace('Not a HighLevelInterface object',w)
        return # None
    else:
        # Let the widget handle the event.
        return # None
.. @+node:ekr.20061031131434.146: *7* k.masterKeyHandler & helpers
master_key_count = 0

def masterKeyHandler (self,event):

    '''This is the handler for almost all key bindings.'''
    
    trace = (False or g.trace_masterKeyHandler) and not g.app.unitTesting
    traceGC = g.trace_masterKeyHandlerGC and not g.app.unitTesting
    verbose = True
    
    k,c = self,self.c ; gui = g.app.gui
    c.check_event(event)
    << define vars >>
    
    assert g.isStrokeOrNone(stroke)

    if char in special_keys:
        if trace and verbose: g.trace('char',char)
        return None
    
    if traceGC: g.printNewObjects('masterKey 1')
    if trace and verbose: g.trace('stroke:',repr(stroke),'char:',
        repr(event and event.char),
        'ch:',repr(event and event.char),
        'state',state,'state2',k.unboundKeyAction)

    # Handle keyboard-quit first.
    if k.abortAllModesKey and stroke == k.abortAllModesKey:
        if c.macroCommands.recordingMacro:
            c.macroCommands.endMacro()
            return # (for Tk) 'break'
        else:
            return k.masterCommand(commandName='keyboard-quit',
                event=event,func=k.keyboardQuit,stroke=stroke)

    if k.inState():
        if trace: g.trace('   state %-10s %s' % (stroke,state))
        done,val = k.doMode(event,state,stroke)
        if done: return val

    if traceGC: g.printNewObjects('masterKey 2')
            
    # 2011/02/08: An important simplification.
    if isPlain and k.unboundKeyAction != 'command':
        if self.isAutoCompleteChar(stroke):
            if trace: g.trace('autocomplete key',stroke)
        else:
            if trace: g.trace('inserted %-10s (insert/overwrite mode)' % (stroke))
            return k.handleUnboundKeys(event,char,stroke)

    # 2011/02/08: Use getPandBindings for *all* keys.
    si = k.getPaneBinding(stroke,w)
    if si:
        assert g.isShortcutInfo(si),si
        if traceGC: g.printNewObjects('masterKey 3')
        if trace: g.trace('   bound',stroke,si.func.__name__)
        return k.masterCommand(event=event,
            commandName=si.commandName,func=si.func,stroke=si.stroke)
    else:
        if traceGC: g.printNewObjects('masterKey 4')
        if trace: g.trace(' unbound',stroke)
        return k.handleUnboundKeys(event,char,stroke)
.. @+node:ekr.20061031131434.147: *8* << define vars >>
w = event and event.widget
char = event and event.char or ''
stroke = event and event.stroke or None
w_name = c.widget_name(w)
state = k.state.kind

special_keys = (
    'Alt_L','Alt_R',
    'Caps_Lock','Control_L','Control_R',
    'Meta_L','Meta_R', # Meta support.
    'Num_Lock',
    'Shift_L','Shift_R',
    'Win_L','Win_R',
)

self.master_key_count += 1

isPlain =  k.isPlainKey(stroke)
.. @+node:ekr.20061031131434.108: *8* callStateFunction
def callStateFunction (self,event):

    trace = False and not g.unitTesting
    k = self ; val = None 
    ch = event and event.char or ''
    stroke = event and event.stroke or ''

    if trace: g.trace(k.state.kind,'ch',ch,'stroke',stroke,
        'ignore_unbound_non_ascii_keys',k.ignore_unbound_non_ascii_keys)
        
    if k.state.kind == 'auto-complete':
        # 2011/06/17.
        # k.auto_completer_state_handler returns 'do-standard-keys' for control keys.
        val = k.state.handler(event)
        if trace: g.trace('auto-complete returns',repr(val))
        return val
    elif k.state.kind:
        if (
            k.ignore_unbound_non_ascii_keys and
            len(ch) == 1 and # 2011/04/01
            ch and ch not in ('\b','\n','\r','\t') and
            (ord(ch) < 32 or ord(ch) > 128)
        ):
            # g.trace('non-ascii',ord(ch))
            pass
        elif k.state.handler:
            val = k.state.handler(event)
            if val != 'continue':
                k.endCommand(k.commandName)
        else:
            g.es_print('no state function for',k.state.kind,color='red')

    return val
.. @+node:ekr.20091230094319.6244: *8* doMode
def doMode (self,event,state,stroke):

    trace = False and not g.unitTesting
    k = self

    # First, honor minibuffer bindings for all except user modes.
    if state in ('getArg','getFileName','full-command','auto-complete'):
        if k.handleMiniBindings(event,state,stroke):
            return True,'break'

    # Second, honor general modes.
    if state == 'getArg':
        return True,k.getArg(event,stroke=stroke)
    elif state == 'getFileName':
        return True,k.getFileName(event)
    elif state in ('full-command','auto-complete'):
        # Do the default state action.
        if trace: g.trace('calling state function',k.state.kind)
        val = k.callStateFunction(event) # Calls end-command.
        if trace: g.trace('state function returns',repr(val))
        if val == 'do-standard-keys':
            return False,None # 2011/06/17.
        else:
            return True,'break'

    # Third, pass keys to user modes.
    d =  k.masterBindingsDict.get(state)
    if d:
        assert g.isStrokeOrNone(stroke)
        si = d.get(stroke)
        if si:
            assert g.isShortcutInfo(si),si
            if trace: g.trace('calling generalModeHandler',stroke)
            k.generalModeHandler (event,
                commandName=si.commandName,func=si.func,
                modeName=state,nextMode=si.nextMode)
            return True,'break'
        else:
            # New in Leo 4.5: unbound keys end mode.
            # if trace: g.trace('unbound key ends mode',stroke,state)
            g.warning('unbound key ends mode',stroke) # 2011/02/02
            k.endMode()
            return False,None
    else:
        # New in 4.4b4.
        handler = k.getStateHandler()
        if handler:
            if trace: g.trace('handler',handler)
            handler(event)
        else:
            if trace: g.trace('No state handler for %s' % state)
        return True,'break'
.. @+node:ekr.20091230094319.6240: *8* getPaneBinding
def getPaneBinding (self,stroke,w):

    trace = False and not g.unitTesting
    verbose = False
    k = self ; w_name = k.c.widget_name(w)
    # keyStatesTuple = ('command','insert','overwrite')
    state = k.unboundKeyAction
    
    assert g.isStroke(stroke)

    if trace: g.trace('w_name',repr(w_name),'stroke',stroke,'w',w,
        'isTextWidget(w)',g.app.gui.isTextWidget(w))

    for key,name in (
        # Order here is similar to bindtags order.
        ('command',None),
        ('insert',None),
        ('overwrite',None),
        ('button',None),
        ('body','body'),
        ('text','head'), # Important: text bindings in head before tree bindings.
        ('tree','head'),
        ('tree','canvas'),
        ('log', 'log'),
        ('text','log'),
        ('text',None),
        ('all',None),
    ):
        if (
            # key in keyStatesTuple and isPlain and k.unboundKeyAction == key or
            name and w_name.startswith(name) or
            key in ('command','insert','overwrite') and state == key or # 2010/02/09
            key in ('text','all') and g.app.gui.isTextWidget(w) or
            key in ('button','all')
        ):
            d = k.masterBindingsDict.get(key,{})
            if trace and verbose:
                # g.trace('key',key,'name',name,'stroke',stroke,'stroke in d.keys',stroke in d)
                g.trace('key: %7s name: %6s stroke: %10s in keys: %s' %
                    (key,name,stroke,stroke in d))
                # g.trace(key,'keys',g.listToString(list(d.keys()),sort=True)) # [:5])
            if d:
                si = d.get(stroke)
                if si:
                    assert si.stroke == stroke,'si: %s stroke: %s' % (si,stroke)
                        # masterBindingsDict: keys are KeyStrokes
                    assert g.isShortcutInfo(si),si
                    table = ('previous-line','next-line',)
                    if key == 'text' and name == 'head' and si.commandName in table:
                        if trace: g.trace('***** special case',si.commandName)
                    else:
                        if trace: g.trace('key: %7s name: %6s  found %s = %s' % (
                            key,name,repr(si.stroke),si.commandName))
                        return si

    return None
.. @+node:ekr.20061031131434.152: *8* handleMiniBindings
def handleMiniBindings (self,event,state,stroke):

    k = self ; c = k.c
    trace = (False or g.trace_masterKeyHandler) and not g.app.unitTesting

    # Special case for bindings handled in k.getArg:
        
    assert g.isStroke(stroke)

    if state in ('getArg','full-command'):
        if stroke in ('\b','BackSpace','\r','Linefeed','\n','Return','\t','Tab','Escape',):
            return False
        if k.isFKey(stroke):
            return False

    if not state.startswith('auto-'):
        # New in Leo 4.5: ignore plain key binding in the minibuffer.
        if not stroke or k.isPlainKey(stroke):
            if trace: g.trace('plain key',stroke)
            return False
        # New in Leo 4.5: The minibuffer inherits 'text' and 'all' bindings
        # for all single-line editing commands.
        for pane in ('mini','all','text'):
            d = k.masterBindingsDict.get(pane)
            if d:
                si = d.get(stroke)
                if si:
                    assert si.stroke == stroke,'si: %s stroke: %s' % (si,stroke)
                        # masterBindingsDict: keys are KeyStrokes
                    assert g.isShortcutInfo(si),si
                    if si.commandName == 'replace-string' and state == 'getArg':
                        if trace: g.trace('%s binding for replace-string' % (pane),stroke)
                        return False # Let getArg handle it.
                    elif si.commandName not in k.singleLineCommandList:
                        if trace: g.trace('%s binding terminates minibuffer' % (
                            pane),si.commandName,stroke)
                        k.keyboardQuit()
                    else:
                        if trace: g.trace(repr(stroke),'mini binding',si.commandName)
                        c.minibufferWantsFocus() # New in Leo 4.5.
                    # Pass this on for macro recording.
                    k.masterCommand(commandName=si.commandName,event=event,func=si.func,stroke=stroke)
                    # Careful: the command could exit.
                    if c.exists and not k.silentMode:
                        c.minibufferWantsFocus()
                    return True

    return False
.. @+node:ekr.20110209083917.16004: *8* isAutoCompleteChar
def isAutoCompleteChar (self,stroke):
    
    '''Return True if stroke is bound to the auto-complete in
    the insert or overwrite state.'''

    k = self ; state = k.unboundKeyAction
    
    assert g.isStrokeOrNone(stroke)
    
    if stroke and state in ('insert','overwrite'):
        for key in (state,'body','log','text','all'):
            d = k.masterBindingsDict.get(key,{})
            if d:
                si = d.get(stroke)
                if si:
                    assert si.stroke == stroke,'si: %s stroke: %s' % (si,stroke)
                    assert g.isShortcutInfo(si),si
                    if si.commandName == 'auto-complete':
                        return True
    return False
.. @+node:ekr.20080510095819.1: *8* k.handleUnboundKeys
def handleUnboundKeys (self,event,char,stroke):

    trace = False and not g.unitTesting
    verbose = True
    k = self ; c = k.c
    modesTuple = ('insert','overwrite')
    
    # g.trace('self.enable_alt_ctrl_bindings',self.enable_alt_ctrl_bindings)
    
    assert g.isStroke(stroke)

    if trace and verbose: g.trace('ch: %s, stroke %s' % (
        repr(event and event.char),repr(stroke)))

    # g.trace('stroke',repr(stroke),'isFKey',k.isFKey(stroke))

    if k.unboundKeyAction == 'command':
        # Ignore all unbound characters in command mode.
        w = g.app.gui.get_focus(c)
        if w and g.app.gui.widget_name(w).lower().startswith('canvas'):
            c.onCanvasKey(event)
        if trace: g.trace('ignoring unbound character in command mode',stroke)
        return None
        
    elif stroke.isFKey():
        if trace: g.trace('ignoring F-key',stroke)
        return None

    elif stroke and k.isPlainKey(stroke) and k.unboundKeyAction in modesTuple:
        # insert/overwrite normal character.  <Return> is *not* a normal character.
        if trace: g.trace('plain key in insert mode',repr(stroke))
        return k.masterCommand(event=event,stroke=stroke)

    elif (not self.enable_alt_ctrl_bindings and
        (stroke.find('Alt+') > -1 or stroke.find('Ctrl+') > -1)
    ):
        # 2011/02/11: Always ignore unbound Alt/Ctrl keys.
        if trace: g.trace('ignoring unbound Alt/Ctrl key',
            repr(char),repr(stroke))
        return None

    elif k.ignore_unbound_non_ascii_keys and (
        len(char) > 1 or
        char not in string.printable # 2011/06/10: risky test?
    ):
        if trace: g.trace('ignoring unbound non-ascii key',
            repr(char),repr(stroke))
        return None

    elif (
        stroke and stroke.find('Escape') != -1 or
        stroke and stroke.find('Insert') != -1
    ):
        # Never insert escape or insert characters.
        if trace: g.trace('ignore Escape/Ignore',stroke)
        return None

    else:
        if trace: g.trace('no func',repr(char),repr(stroke))
        return k.masterCommand(event=event,stroke=stroke)
.. @+node:ekr.20111017132257.15888: *5* Create a script to diff 2 Leo files & create a tree?
.. @+node:ekr.20111026075003.16481: *5* Support ~/.leo_config.py
@language rest

1. Imo, it is time to consider adding a typical "startup" file to Leo,
~/leo_config.py, similar to ~/.emacs or ipython_config.py.  This will
make Leo "thicker", that is more professional/standard.

IPython adds lots of bells and whistles to configuration.  Leo
emulates most of them, but .leo_config.py would be a good addition.
Think of .leo_config.py as a lightweight plugin.

leo_config.py should execute after settings have been parsed, but
before plugins have been loaded.  If desired, leo_config.py may
register "start2" event handlers, which execute after all plugins have
been loaded, just before starting the main Qt event loop.

2. Leo commands form a good framework for other solutions to Qt
stylesheet issues.  Consider the following commands::

- qt-stylesheet-set-global-stylesheet
- qt-stylesheet-append-to-global-stylesheet
- qt-stylesheet-set-widget-stylesheet
- qt-stylesheet-append-to-widget-stylesheet

@language python
.. @+node:ekr.20111021105253.9478: *4* Most important
.. @+node:ekr.20111010122531.15568: *5* * @render-rest, @render-html trees
@language rest

The free_layout and viewrendered plugins are a huge step forward.  But
the lighter/heavier distinction suggests a new way to use them.

Suppose Leo supports @render-rest or @render-html.

This means that all nodes in the tree will have the body pane become a
rendering pane for rST or html.

Imagine LeoDocs completely rendered at all times.

Of course, for specific purposes, say in Leo's scripting chapter, we
might want to override these rendering directives (which should be
allowed in headlines too) with @no-render.

The point is that having *both* the original text *and* the rendered
text be visible is often too heavy: the user usually does not want to
know about the sources: the rendering is good enough.

I suppose for sophisticated users, something like show/hide-body pane
would be good commands to have, but that doesn't matter: those
commands to not increase the burden on the user while she is reading
the (rendered) docs.
.. @+node:ekr.20111019104425.15868: *5* * Render to tex, pdf, rst, etc.
.. @+node:ekr.20111003232155.6988: *5* * Use c.db for marks & expansion bits
This would allow us to eliminate @bool put_expansion_bits_in_leo_files.
.. @+node:ekr.20110921094450.6957: *5* Restart vim project
This requires commands that can be composed.
.. @+node:ekr.20111027103125.16538: *6* * Look at vim video
http://video.google.com/videoplay?docid=8810772602188234059
.. @+node:ekr.20111021035504.9467: *5* Play with PyQt Qtest framework
http://groups.google.com/group/leo-editor/browse_thread/thread/b851e7d9855a57c2

http://www.voom.net/pyqt-qtest-example
.. @+node:ekr.20111027143736.16558: *5* Work-flow improvements
@nocolor-node

Considering leo.leo
http://groups.google.com/group/leo-editor/browse_thread/thread/e3ddbe650fc9525b/290e97c593ee950a

> > I think there's a way to search across Leo files: quicksearch multiple
> > files ... ?

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

Leo has to have this :-)  It's on my list.  It would be a great way to
find @button nodes.

Furthermore, global searches for attributes in docstrings have
immediate uses.

On the urgent to-do list: drive all aspects of the Nav pane using
keystrokes.

I just reviewed @bookmarks--it's perfect for leo.leo.  The following
would replace all.bat:

- @bookmarks
  - leoPy.leo (url in body)
  - leoPlugins.leo
  - leoSettings.leo
  - myLeoSettings.leo
  - leoDocs.leo
  - leoDist.leo
  - scripts.leo

This way could be said to be much better than all.bat: nothing gets
loaded until needed.  Therefore, even more files could be added.

Imo, URL's in bookmark trees should support {{expression}} notations.

For example, I want the following to work in the body text of a node
in an @bookmarks tree::

file://{{g.os_path_finalize_join(g.app.loadDir,'..','doc','LeoDocs.leo').replace('\\','/')}}

Not bad, eh?  Or maybe the URL logic should do the replace('\\','/')
automatically...

Got to go.  We are nearing a tipping point in Leo's workflow... 
.. @+node:ekr.20110921094450.6954: *4* Important
.. @+node:ekr.20110921094450.6955: *5* Windows
Leo should support more new windows.


Terry's plugins have also created nifty new windows.

Here are some other ideas:

A.  Hosting the PyQt demo inside Leo.

B. Making an OpenGl window an official Leo window. This might be the start of a
   prototype for "Blender in a Leo Window".
.. @+node:ekr.20110528034751.18272: *6* Support for tabifying Leo's core panes
Collaborate with Terry.

A. Place separate body editors in free_layout areas. This should *easy* to
   do! Almost nothing changes in the code, but the visual effect should be
   much better.

B. Allow any pane to be "tabified" (placed in a tab in the Log pane) and
   "untabified." There are a few details to be handled, but nothing major.
   
C. Labels for panes.
.. @+node:ekr.20110525112110.18402: *6* Ideas:
.. @+node:ekr.20101104191857.5820: *7* QWebView makes Leo a presentation tool
http://groups.google.com/group/leo-editor/browse_thread/thread/4ea2d3f7d2c68106#

Ville:

Create one QWebView window, zoom it in to have large fonts.

Create @button that converts current node containing restructuredtext to html,
and pushes that html to QWebView.

Voila', instant presentation tool. The webview window would be on projector, and
leo would be in your private computer. You can easily edit the text, or find new
interesting slides to present in privacy of your own screen.

.. @+node:ekr.20060227123536: *7* Tiddlywiki and related comments about rendering body text (Mulder)
@nocolor
http://sourceforge.net/forum/message.php?msg_id=3578252
By: bwmulder

I have been thinking for a while that it ought to be possible to somehow  to
unite Leo with wiki features (my thinking is still vague at this point).

If you look at systems like Tiddlywiki (http://www.tiddlywiki.com/) you will
find that they already pretty much provide all the formatting features mentioned
in the article.

MoinMoin, another wiki (http://moinmoin.wikiwikiweb.de), has started to use
a graphical interface for editing in the latest version.

Maybe Leo can be split up into three components:

1. A storage component is responsible for storing nodes. Currently, this is
just memory, but databases like shelve, Zope or sqllite should also be possible.

2. The control component is responsible for converting from the internal format
to external files which can be processed by existing compilers, searching within
a document, and the like.

3. A display component is responsible for interfacing with the user. If can
be TK, but it can also be something like the Tiddlywiki interface, which immediately
shows the formatting applied to text.

I don't know much about javascript, so I would have to learn more about this
language before doing anything in this direction.

As an intermediate step, maybe we could allow mixing RST processing with regular
program text.  Leo would produce two documents out of a source file: a version
for the compiler in plain ascii, and an HTML file for reading the source.
.. @+node:ekr.20110921094450.6958: *5* Bridges
There are already bridges for vim, emacs, ipython and docutils, but I think more
can be done.

The first thought is to improve Leo's inter-process communication capabilities.
I'm not sure what that entails...

As a blue-sky project, could Leo interact with the window manager in order to
resize vim, say, to it automatically tracks Leo's body pane (or any other pane).
.. @+node:ekr.20110921094450.6959: *5* Code tools
Analysis, checking, refactoring and other code-level tools are a natural for
Leo. When the new-lint project is mature, it could be folded into Leo.

* Rewrite the beautify-python command using a much simpler tokenizer.

.. @+node:ekr.20110921094450.6960: *5* Documentation tools
Perhaps Leo's documentation could be built primarily from docstrings. In any
event, documentation tools are always needed.
.. @+node:ekr.20111009230326.7036: *4* Important/Later
.. @+node:ekr.20120226183512.10202: *5* Use QSignalSpy?
QSignalSpy: doesn't exist on PyQt?
.. @+node:ekr.20111019104425.15863: *5* Use pygments for syntax coloring?
.. @+node:ekr.20111009080647.15614: *6* pygments test
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
import time

fn = g.os_path_finalize_join(g.app.loadDir,'leoCommands.py')
s = open(fn).read()

# s = 'print "Hello World"'

t1 = time.time()

s2 = highlight(s,PythonLexer(),HtmlFormatter())

t2 = time.time()

print(len(s),len(s2),'%2.2f sec' % (t2-t1))
.. @+node:ekr.20111009080647.15615: *6* pygments install error on pthon26 (python3.2 no problem)
@nocolor-node

c:\apps\pygments>python26 setup.py install

c:\apps\pygments>c:\python26\python.exe setup.py install
running install
running bdist_egg
running egg_info
creating Pygments.egg-info
writing Pygments.egg-info\PKG-INFO
writing top-level names to Pygments.egg-info\top_level.txt
writing dependency_links to Pygments.egg-info\dependency_links.txt
writing entry points to Pygments.egg-info\entry_points.txt
writing manifest file 'Pygments.egg-info\SOURCES.txt'
reading manifest file 'Pygments.egg-info\SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'Pygments.egg-info\SOURCES.txt'
installing library code to build\bdist.win32\egg
running install_lib
running build_py
creating build\bdist.win32
creating build\bdist.win32\egg
creating build\bdist.win32\egg\pygments
copying build\lib\pygments\cmdline.py -> build\bdist.win32\egg\pygments
copying build\lib\pygments\console.py -> build\bdist.win32\egg\pygments
copying build\lib\pygments\filter.py -> build\bdist.win32\egg\pygments
creating build\bdist.win32\egg\pygments\filters
copying build\lib\pygments\filters\__init__.py -> build\bdist.win32\egg\pygments\filters
copying build\lib\pygments\formatter.py -> build\bdist.win32\egg\pygments
creating build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\bbcode.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\html.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\img.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\latex.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\other.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\rtf.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\svg.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\terminal.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\terminal256.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\_mapping.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\formatters\__init__.py -> build\bdist.win32\egg\pygments\formatters
copying build\lib\pygments\lexer.py -> build\bdist.win32\egg\pygments
creating build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\agile.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\asm.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\compiled.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\dotnet.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\functional.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\hdl.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\math.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\other.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\parsers.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\postgres.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\pypylog.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\special.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\templates.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\text.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\web.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\_asybuiltins.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\_clbuiltins.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\_luabuiltins.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\_mapping.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\_phpbuiltins.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\_postgres_builtins.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\_vimbuiltins.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\lexers\__init__.py -> build\bdist.win32\egg\pygments\lexers
copying build\lib\pygments\plugin.py -> build\bdist.win32\egg\pygments
copying build\lib\pygments\scanner.py -> build\bdist.win32\egg\pygments
copying build\lib\pygments\style.py -> build\bdist.win32\egg\pygments
creating build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\autumn.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\borland.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\bw.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\colorful.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\default.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\emacs.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\friendly.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\fruity.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\manni.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\monokai.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\murphy.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\native.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\pastie.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\perldoc.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\tango.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\trac.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\vim.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\vs.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\styles\__init__.py -> build\bdist.win32\egg\pygments\styles
copying build\lib\pygments\token.py -> build\bdist.win32\egg\pygments
copying build\lib\pygments\unistring.py -> build\bdist.win32\egg\pygments
copying build\lib\pygments\util.py -> build\bdist.win32\egg\pygments
copying build\lib\pygments\__init__.py -> build\bdist.win32\egg\pygments
byte-compiling build\bdist.win32\egg\pygments\cmdline.py to cmdline.pyc

SyntaxError: ('invalid syntax', ('build\\bdist.win32\\egg\\pygments\\cmdline.py', 133, 43,
'print("%s not found!" % what, file=sys.stderr)\n'))

byte-compiling build\bdist.win32\egg\pygments\console.py to console.pyc
byte-compiling build\bdist.win32\egg\pygments\filter.py to filter.pyc
byte-compiling build\bdist.win32\egg\pygments\filters\__init__.py to __init__.pyc
byte-compiling build\bdist.win32\egg\pygments\formatter.py to formatter.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\bbcode.py to bbcode.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\html.py to html.pyc

SyntaxError: ('invalid syntax', ('build\\bdist.win32\\egg\\pygments\\formatters\\html.py', 472, 88, "
       'using current directory as base for the CSS file name', file=sys.stderr)\n"))

byte-compiling build\bdist.win32\egg\pygments\formatters\img.py to img.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\latex.py to latex.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\other.py to other.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\rtf.py to rtf.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\svg.py to svg.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\terminal.py to terminal.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\terminal256.py to terminal256.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\_mapping.py to _mapping.pyc
byte-compiling build\bdist.win32\egg\pygments\formatters\__init__.py to __init__.pyc
byte-compiling build\bdist.win32\egg\pygments\lexer.py to lexer.pyc
SyntaxError: ('invalid syntax', ('build\\bdist.win32\\egg\\pygments\\lexer.py', 40, 30, 'class Lexer(object, metaclass=L
exerMeta):\n'))

byte-compiling build\bdist.win32\egg\pygments\lexers\agile.py to agile.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\asm.py to asm.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\compiled.py to compiled.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\dotnet.py to dotnet.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\functional.py to functional.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\hdl.py to hdl.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\math.py to math.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\other.py to other.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\parsers.py to parsers.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\postgres.py to postgres.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\pypylog.py to pypylog.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\special.py to special.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\templates.py to templates.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\text.py to text.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\web.py to web.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\_asybuiltins.py to _asybuiltins.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\_clbuiltins.py to _clbuiltins.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\_luabuiltins.py to _luabuiltins.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\_mapping.py to _mapping.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\_phpbuiltins.py to _phpbuiltins.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\_postgres_builtins.py to _postgres_builtins.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\_vimbuiltins.py to _vimbuiltins.pyc
byte-compiling build\bdist.win32\egg\pygments\lexers\__init__.py to __init__.pyc
byte-compiling build\bdist.win32\egg\pygments\plugin.py to plugin.pyc
byte-compiling build\bdist.win32\egg\pygments\scanner.py to scanner.pyc
byte-compiling build\bdist.win32\egg\pygments\style.py to style.pyc

SyntaxError: ('invalid syntax', ('build\\bdist.win32\\egg\\pygments\\style.py', 107, 30,

'class Style(object, metaclass=StyleMeta):\n'))

byte-compiling build\bdist.win32\egg\pygments\styles\autumn.py to autumn.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\borland.py to borland.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\bw.py to bw.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\colorful.py to colorful.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\default.py to default.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\emacs.py to emacs.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\friendly.py to friendly.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\fruity.py to fruity.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\manni.py to manni.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\monokai.py to monokai.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\murphy.py to murphy.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\native.py to native.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\pastie.py to pastie.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\perldoc.py to perldoc.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\tango.py to tango.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\trac.py to trac.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\vim.py to vim.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\vs.py to vs.pyc
byte-compiling build\bdist.win32\egg\pygments\styles\__init__.py to __init__.pyc
byte-compiling build\bdist.win32\egg\pygments\token.py to token.pyc
byte-compiling build\bdist.win32\egg\pygments\unistring.py to unistring.pyc
byte-compiling build\bdist.win32\egg\pygments\util.py to util.pyc
byte-compiling build\bdist.win32\egg\pygments\__init__.py to __init__.pyc
creating build\bdist.win32\egg\EGG-INFO
copying Pygments.egg-info\PKG-INFO -> build\bdist.win32\egg\EGG-INFO
copying Pygments.egg-info\SOURCES.txt -> build\bdist.win32\egg\EGG-INFO
copying Pygments.egg-info\dependency_links.txt -> build\bdist.win32\egg\EGG-INFO
copying Pygments.egg-info\entry_points.txt -> build\bdist.win32\egg\EGG-INFO
copying Pygments.egg-info\not-zip-safe -> build\bdist.win32\egg\EGG-INFO
copying Pygments.egg-info\top_level.txt -> build\bdist.win32\egg\EGG-INFO
creating dist
creating 'dist\Pygments-1.4dev_20111009-py2.6.egg' and adding 'build\bdist.win32\egg' to it
removing 'build\bdist.win32\egg' (and everything under it)
Processing Pygments-1.4dev_20111009-py2.6.egg
creating c:\python26\lib\site-packages\Pygments-1.4dev_20111009-py2.6.egg
Extracting Pygments-1.4dev_20111009-py2.6.egg to c:\python26\lib\site-packages

SyntaxError: ('invalid syntax', ('c:\\python26\\lib\\site-packages\\Pygments-1.4dev_20111009-py2.6.egg\\pygments\\cmdlin
e.py', 133, 43, '        print("%s not found!" % what, file=sys.stderr)\n'))

SyntaxError: ('invalid syntax', ('c:\\python26\\lib\\site-packages\\Pygments-1.4dev_20111009-py2.6.egg\\pygments\\lexer.
py', 40, 30, 'class Lexer(object, metaclass=LexerMeta):\n'))

SyntaxError: ('invalid syntax', ('c:\\python26\\lib\\site-packages\\Pygments-1.4dev_20111009-py2.6.egg\\pygments\\style.
py', 107, 30, 'class Style(object, metaclass=StyleMeta):\n'))

SyntaxError: ('invalid syntax', ('c:\\python26\\lib\\site-packages\\Pygments-1.4dev_20111009-py2.6.egg\\pygments\\format
ters\\html.py', 472, 88, "                          'using current directory as base for the CSS file name', file=sys.st
derr)\n"))

Removing pygments 1.1.1 from easy-install.pth file
Adding Pygments 1.4dev-20111009 to easy-install.pth file
Installing pygmentize-script.py script to c:\python26\Scripts
Installing pygmentize.exe script to c:\python26\Scripts
Installing pygmentize.exe.manifest script to c:\python26\Scripts

Installed c:\python26\lib\site-packages\pygments-1.4dev_20111009-py2.6.egg
Processing dependencies for Pygments==1.4dev-20111009
Finished processing dependencies for Pygments==1.4dev-20111009

c:\apps\pygments>
.. @+node:ekr.20110930075237.15472: *5* * Improving Leo: think inside the box
@language rest,
.. @+node:ekr.20111019104425.15886: *6* Windows
Terry's work is an enabler.  Just as Blender supports many kinds of
windows, so too should Leo.  Up until now, the outline, body and log
panes have been the only "official" panes.  The rendering pane soon
will be fully official.
.. @+node:ekr.20111019104425.15890: *6* Bridges
This could be an important "new" direction.  True, there are already
bridges for vim, emacs, ipython and docutils, but I think more can be
done.

The first thought is to improve Leo's inter-process communication
capabilities.  I'm not sure what that entails...

As a blue-sky project, could Leo interact with the window manager in
order to resize vim, say, to it automatically tracks Leo's body pane
(or any other pane).
.. @+node:ekr.20111019104425.15887: *6* Host the PyQt demo inside Leo
.. @+node:ekr.20111019104425.15888: *6* Support an openGL window in Leo
.. @+node:ekr.20111019104425.15891: *6* Code tools
Analysis, checking, refactoring and other code-level tools are a
natural for Leo.  When the new-lint project is mature, it could be
folded into Leo.

.. @+node:ekr.20110930075237.15473: *6* Kent's suggestions
@nocolor-node


- 
.. @+node:ekr.20111019104425.15892: *7* Buttons
Enhance the button machinery to allow mixed case, spaces, colors.
Provide cascading rclick capability: rclick on an rclick list ...
.. @+node:ekr.20111019104425.15893: *7* Templating
Solid, simple implementation of one of the standard template engines
providing intuitive template nodes, variable definitions, and rendering options.

.. @+node:ekr.20111019104425.15894: *7* Wizards
Leverage the template capability to offer form-based content creation:
- create a plugin
- generate a test node
- generate a wizard :-]
.. @+node:ekr.20111019104425.15895: *7* LeoFS
- create a Leo implementation of pyfilesystem
http://packages.python.org/fs/implementersguide.html

.. @+node:ekr.20111019104425.15896: *7* Enhansed node attributes: ctime, atime, mtime
.. @+node:ekr.20111019104425.15897: *7* Persistent tab status
Resume editing with same tabs open when using tabbed gui.

.. @+node:ekr.20111019104425.15898: *7* Persistent pane layout
.. @+node:ekr.20110930075237.15474: *6* Matt Wilkie
@nocolor-node

As for user interface, I'd love to see myLeoSettings with a checkbox
interface and filter bar at the top, and a feature to "merge or reset
from LeoSettings".
... or maybe something like Firefox's "about:config" would be better
suited (and probably faster to build).
.. @+node:ekr.20111027103125.16546: *5* What's next
@nocolor-node

http://groups.google.com/group/leo-editor/browse_thread/thread/19bfe6daf2b324/c50a750606d64f77

Fossil (SCCS) and Leo.

> I think that the interaction of Fossil + Leo could
> solve the idea of having external files in a single "Leo document" that
> would be really a fossil sqlite repository with all the external files in
> it, but syncronizable with the outside world. This kind of instantiated
> image of files in a moment of time in Fossil + Leo, would be like the
> instantiated image of objects in a moment of time of Smalltalk.
.. @+node:ekr.20111014134653.15672: *5* Search across multiple Leo files
http://groups.google.com/group/leo-editor/browse_thread/thread/cf5ab54f29a6c128
.. @+node:ekr.20110929074744.15449: *5* generalize show/hide/select gui elements commands
@nocolor-node

tab-cycle-next makes the following not so important

There is a relationship here with mouseless
programming.  It would seem that all visual elements, especially those
that may exist in multiple versions, must have a name or other
description suitable for generalized commands.

The user might want multiple rendering panes, especially if
one or more are locked.  Without a description, there is no way to
specify exactly what show/hide-rendering pane does.

I haven't forgotten the autocompleter docs.  I'll get to them next.
It looks like autocompletion would be the way to generalize the not-
very-effect commands that switch focus from one ui element to
another.  A related benefit is one generalized command might be more
convenient to use than the present flavors of (buggy) cycle-focus
commands.

In short, contemplating generalized windows leads us to generalized
select/delete/show/hide commands, based on autocompletion, that work
on various ui elements.  This looks like the next project. 
.. @+node:ekr.20110621085435.6532: *5* Request: have equal-sized-panes resize vr pane
@nocolor-node

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

I noticed that when the renderpane is active it is not affected by the
Window>Equal Sized Panes command. Only the Outline and Body Panes are affected -
just as described in
http://webpages.charter.net/edreamleo/outlines.html#resizing-panes. However from
the user perspective the current behaviour appears as though the command is only
partially successful. Since there are specific commands to contract/expand the
log pane, shouldn't the log and render panes also be affected by the
Window>Equal Sized Panes command?


I suppose so.  I would prefer to wait for Terry to finish his
pane-generalization code before dealing with this.
.. @+node:ekr.20111018104244.15928: *5* Improve Find panel
@nocolor-node

- All open files.
- Show all results as in quicksearch.
.. @+node:ekr.20111017132257.15883: *5* Possible to use IPython completion?
@language rest

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

I don't know if this is relevant or not, but the IPython autocompletion
capability is awesome.

If I'm investigating code I tend to do the following.

list the modules in a package:
In [1]: from fs import <tab>

this lists the modules, in a package so I pick one
In [1]: from fs import osfs
<the osfs entry is tab-completable>

now I can check the usage of fs.osfs
In [2]: osfs? <enter>

or the source:
In [2]: osfs?? <enter>

or the contents of the module
In [2]: osfs. <tab>

I can instantiate a class:
In [3]: myfs = osfs.OSFS('/')

and examine the ivars and methods:
In [4]: myfs. <tab>

It is such an efficient way to learn and remember the details of code.

I know a bunch of work was done on Leo/IPython integration, don't
know the current status, my wish of synchronized IPython and Leo
may well be one of the many granted wishes I have forgotten about.

I certainly think IPython autocompletion is the gold standard. 
.. @+node:ekr.20110929074744.15499: *5* Allow more panes to be part of the free_layout "action" buttons
.. @+node:ekr.20111009230326.7037: *5* Leo-to-json script for IPython?
.. @+node:ekr.20110527084258.18378: *6* New file format
@nocolor-node

** remove window state, expansion status etc.
   stuff from .leo file, and move that over to c.db

- <attr> solves pickle problem.

* Treat gnx's as strings: don't parse them.
  http://mail.google.com/mail/#inbox/12f3d4950fbabeea
  
* Don't save expansion bits in uA if not saving expansion bits. It's illogical
  to save bits in uA's if they aren't save in in the <v> elements.
  
    @bool put_expansion_bits_in_leo_files = False

- Use uuid's?

- Remove spaces from user names.

.. @+node:ekr.20110609042343.16546: *7* Notes
.. @+node:ekr.20110609042343.16548: *8* File format, v3 draft 5
@language rest
@pagewidth 65

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

Leo's file format: version 3, draft 5

This draft is intended to reflect minimal changes from Leo's
present file format, but with improvements mentioned at the
Ashland sprint and developed afterward.

This draft covers only Leo's xml format, but it may be adapted
for use as a json file format.

This draft contains significant invention by EKR. See the next
section. Don't panic: it can be changed :-)

Summary of changes from draft 4
===============================

- Elements will be renamed as follows::

    old         new
    ====        ====
    <vnodes>    <directed-acyclic-graph>
    <tnodes>    <data-list>
    <v>         <node>
    <t>         <data>

- Nesting of <node> elements represents the structure of the DAG,
  just as nesting of <v> elements does at present.
  
- Thus, there will be no <edge> elements.

- Headlines will move from <v> elements to <data> elements.
  This "normalizes" the data: headlines will appear only once.
  
- <attr> elements will represent uA's.  A full discussion below.

  Ideally, I would like to support only string and json formats
  for <attr> elements.  This is open to discussion. 

- Only <node> elements will contain <attr> elements.

- <node> elements for @file nodes will contain
  <at-file-attributes> elements, representing Leo's "hidden
  machinery" attributes.  <at-file-attributes> will contain
  <attr> elements. 

First lines
===========

Leo file will start with the following::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3"
        xmlns:leo="http://www.leo-editor.org/2011/leo"/>
        

No session data
===============

There will be no <globals>, <preferences> or
<find_panel_settings> elements. All such "session data" will be
restored from the cache, or from defaults if caching is disabled.

**Important**: there is no representation of expansion state or
the currently selected node anywhere in the .leo file.
Eliminating these data is contingent on having Leo work well with
caching disabled.

Note: marks are more than session data. They must appear within
<node> elements.


Summary of format
=================

.leo files will have the following form, with similar indentation::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3" xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <directed-acyclic-graph>
        <node id="gnx">
            <!-- contained node elements, if any.
        </node>
        <node id="gnx">
            <!-- contained v elements, if any.
        </node>
        ...
    </directed-acyclic-graph>
    <data-list>
        <data id="gnx">
            <!-- marked attribute appears only if the tnode/vnode is marked -->
            <head>headline text</head>
            <attr key="marked">1</attr>
            <!-- uA's... -->
            <!-- format="string" is the default -->
            <attr key="a">a string</attr>
            <attr key="b" format="json">a json string</attr>
            <attr key="c" format="pickle">a pickled string</attr>
            <attr key="d" format="binhex">a binhexed string</attr>
            ...
            <body>body text</body>
        </data>
        ...
    </data-list>
    </leo_file>

<attr> elements
===============

<attr> elements will one of the following forms::

    <attr key="a">a unicode string</attr>
    <attr key="b" format="json">a json string</attr>
    <attr key="c" format="pickle">a json string</attr>
    <attr key="d" format="binhex">a binhexed string</attr>
    
The value will be a string by default.

If possible, I would like to support only the string and json
formats. This would make the data as transparent as possible.
Please mentally amend the following discussion...

uA's that start with "binhex_" will use the binhex format. This
prefix must be retained in the type field, so the read code can
restore them.

If the value is not a string, and there is no "binhex_" prefix,
the write code will use format="json" if json.dumps succeeds, and
will use format="pickle" otherwise.

No <attr> element will be written if both json.dumps and
pickle.dumps fail. Attribute failures will create a warning for
the plugin developer.
    
Descendant attributes in @file trees
====================================

Descendants of @file nodes do not appear in .leo files. Thus,
important data must be stored in the so-called hidden machinery:
attributes of the @file node.

The <at-file-attributes> element may optionally be contained in
the <node> element for @file nodes::

    <at-file-attributes>
        <attr>key="ua-name"
            format="(empty)/json/pickle/binhex"
            gnx="gnx">value
        </attr>
        ...
    </at-file-attributes>
.. @+node:ekr.20110421132230.6107: *8* File format, v3 draft 4
@language rest
@pagewidth 65

Leo's file format: version 3, draft 4

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

Here is the latest version, with the graphml stuff removed.

This draft is intended to reflect our recent discussions, with no
new invention from me. All comments and corrections welcome.

This draft covers only Leo's xml format, but it may be adapted
for use as a json file format.

Recent changes
==============

- Removed graphml stuff, including leo: prefixes.

- Used "key" in <attr> elements (and "type" in <edge> elements.)

First lines
===========

Leo file will start with the following::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3"
        xmlns:leo="http://www.leo-editor.org/2011/leo"/>
        

No session data
===============

There will be no <globals>, <preferences> or
<find_panel_settings> elements. All such "session data" will be
restored from the cache, or from defaults if caching is disabled.

**Important**: there is no representation of expansion state or
the currently selected node anywhere in the .leo file.
Eliminating these data is contingent on having Leo work well with
caching disabled.

Note: marks are more than session data. They must appear
somewhere within <node> elements.


Summary of format
=================

.leo files will have the following form, with similar indentation::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3" xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <graph>
    <nodes>
        <!-- marked attribute appears only if the vnode is marked -->
        <node id="gnx"> 
            <head>headline text</head>
            <attr key="marked">1</attr>
            <!-- uA's... -->
            <!-- format="string" is the default -->
            <attr key="a">a string</attr>
            <attr key="b" format="json">a json string</attr>
            <attr key="c" format="pickle">a pickled string</attr>
            <attr key="d" format="binhex">a binhexed string</attr>
            ...
            <body>body text</body>
        </node>
        ...
        <!-- @file nodes contain a <data> package -->
        <node id="gnx">
            <head>@file x.py</head>
            ...
            <at-file-attributes>
                <attr>key="ua-name"
                    format="(empty)/json/pickle/binhex"
                    gnx="gnx">value
                </attr>
                ...
            </at-file-attributes>
        </node>
        ...
    </nodes>
    <edges>
        <edge type="child" from="gnx" to="gnx"</edge>
        ...
    </edges>
    </graph>
    </leo_file>

<attr> elements
===============

<attr> elements will one of the following forms::

    <attr key="a">a unicode string</attr>
    <attr key="b" format="json">a json string</attr>
    <attr key="c" format="pickle">a json string</attr>
    <attr key="d" format="binhex">a binhexed string</attr>
    
That is, the value will be a string by default.

uA's that start with "binhex_" will use the binhex format. This
prefix must be retained in the type field, so the read code can
restore them.

If the value is not a string, and there is no "binhex_" prefix,
the write code will use format="json" if json.dumps succeeds, and
will use format="pickle" otherwise.

No <attr> element will be written if both json.dumps and
pickle.dumps fail. Attribute failures will create a warning for
the plugin developer.

<edge> elements
===============

<edge> elements will have the form::

    <edge type="child" from="gnx" to="gnx"/>
    
Leo will use type="child" to represent Leo's official edges.
Plugins are free to define any type except "child". Examples::

    <edge type="undirected" from="gnx" to="gnx"/>
    <edge type="bidirectional" from="gnx" to="gnx"/>
    <edge type="backlink" from="gnx" to="gnx"/>
    <edge type="myPlugin" from="gnx" to="gnx"/>
    
Descendant attributes in @file trees
====================================

Descendants of @file nodes do not appear in .leo files. Thus,
important data must be stored in the so-called hidden machinery:
attributes of the @file node.

The <at-file-attributes> element may be contained in the
<node> element for @file nodes::

    <at-file-attributes>
        <attr>key="ua-name"
            format="(empty)/json/pickle/binhex"
            gnx="gnx">value
        </attr>
        ...
    </at-file-attributes>
    
In other words, we use the graphml <attr> element, extended with the
gnx attribute, to represent all the uA's in the descendants of @file nodes.
.. @+node:ekr.20110419083918.6104: *8* File format, v3 graphml
@language rest
@pagewidth 65

This draft is intended to reflect our recent discussions, with no
new invention from me. All comments and corrections welcome.

The draft is also intended to be compatible with graphml.

This draft covers only Leo's xml format, but it may be adapted
for use as a json file format.

I am willing to change "type" to "key" in <edge> elements if that
would be preferable.

Recent changes
==============

- Added <graphml> element defining the default namespace.

- Defined the leo namespace for leo-only elements.
    - Renamed <leo_file> to <leo:outline>
    - Renamed <descendant-attributes> to <leo:at-file-attributes>

- Used <leo:at-file-attributes> for marks, removing the special case.

- Enclosed <leo:descendant-attributes> in a (graphml) <data> element.

- Changed the format of the "marked" attribute to be a string-valued attribute.

First lines
===========

Leo file will start with the following::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo:file file_format="3"
        xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <graphml xmlns="http://graphml.graphdrawing.org/xmlns"/>
        

No session data
===============

There will be no <globals>, <preferences> or
<find_panel_settings> elements. All such "session data" will be
restored from the cache, or from defaults if caching is disabled.

**Important**: there is no representation of expansion state or
the currently selected node anywhere in the .leo file.
Eliminating these data is contingent on having Leo work well with
caching disabled.

Note: marks are more than session data. They must appear
somewhere within <node> elements.


Summary of format
=================

.leo files will have the following form, with similar indentation::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo:outline file_format="3" xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <graphml xmlns="http://graphml.graphdrawing.org/xmlns"/>
    <graph>
    <nodes>
        <!-- marked attribute appears only if the vnode is marked -->
        <node id="gnx"> 
            <head>headline text</head>
            <attr key="marked">1</attr>
            <!-- uA's... -->
            <!-- format="string" is the default -->
            <attr key="a">a string</attr>
            <attr key="b" format="json">a json string</attr>
            <attr key="c" format="pickle">a pickled string</attr>
            <attr key="d" format="binhex">a binhexed string</attr>
            ...
            <body>body text</body>
        </node>
        ...
        <!-- @file nodes contain a <data> package -->
        <node id="gnx">
            <head>@file x.py</head>
            ...
            <data>
                <leo:at-file-attributes>
                    <attr>key="ua-name"
                        format="(empty)/json/pickle/binhex"
                        gnx="gnx">value
                    </attr>
                    ...
                </leo:at-file-attributes>
            </data>
        </node>
        ...
    </nodes>
    <edges>
        <edge type="child" from="gnx" to="gnx"</edge>
        ...
    </edges>
    </graph>
    </graphml>
    </leo:outline>

<attr> elements
===============

<attr> elements will one of the following forms::

    <attr key="a">a unicode string</attr>
    <attr key="b" format="json">a json string</attr>
    <attr key="c" format="pickle">a json string</attr>
    <attr key="d" format="binhex">a binhexed string</attr>
    
That is, the value will be a string by default.

uA's that start with "binhex_" will use the binhex format. This
prefix must be retained in the type field, so the read code can
restore them.

If the value is not a string, and there is no "binhex_" prefix,
the write code will use format="json" if json.dumps succeeds, and
will use format="pickle" otherwise.

No <attr> element will be written if both json.dumps and
pickle.dumps fail. Attribute failures will create a warning for
the plugin developer.

<edge> elements
===============

<edge> elements will have the form::

    <edge type="child" from="gnx" to="gnx"/>
    
Leo will use type="child" to represent Leo's official edges.
Plugins are free to define any type except "child". Examples::

    <edge type="undirected" from="gnx" to="gnx"/>
    <edge type="bidirectional" from="gnx" to="gnx"/>
    <edge type="backlink" from="gnx" to="gnx"/>
    <edge type="myPlugin" from="gnx" to="gnx"/>
    
Descendant attributes in @file trees
====================================

Descendants of @file nodes do not appear in .leo files. Thus,
important data must be stored in the so-called hidden machinery:
attributes of the @file node.

The <leo:at-file-attributes> element may be contained in the
<node> element for @file nodes. For compatibility with graphml,
it will enclosed in a data element::
    
    <data>
        <leo:at-file-attributes>
            <attr>key="ua-name"
                format="(empty)/json/pickle/binhex"
                gnx="gnx">value
            </attr>
            ...
        </leo:at-file-attributes>
    </data>
    
In other words, we use the graphml <attr> element, extended with the
gnx attribute, to represent all the uA's in the descendants of @file nodes.
.. @+node:ekr.20090218115025.3: *8* Why are attributes pickled by default?
@nocolor-node

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

> On Wed, Feb 18, 2009 at 12:12 PM, Kent Tenney <ktenney@gmail.com> wrote:
>>
>> Currently, Leo pickles the value of unknown attributes unless
>> the name starts with 'str_'
>>
>> Running the following code in node 'UA'
>>
>> p = c.currentPosition()
>> p.v.u = {'hello':'world', 'str_hello':'world'}
>>
>> results in the following in the .leo file:
>>
>> <v t="ktenney.20090218114928.367" str_hello="world"
>> hello="5505776f726c6471002e"><vh>UA</vh></v>
>>
>> I think this is surprising, Python-centric and contrary to the
>> spirit of Leo as a flexible data management platform.
>
> I suppose your point is that you can't create an arbitrarily named attribute
> with a string value. Does that create a real problem?

It requires a translation layer, either to (un)munge the name or
(un)pickle. Real problem?

Let's say each time I think 'I can use UAs to store that' I change
my mind when I realize my values will be in a pickle. (I really don't
want to name all my attributes str_xxx)

> As far as being Python-centric, can you suggest any other way of converting
> arbitrary data to a text string?

How is it done in any other XML file?
I've not used XML for arbitrary data, but it probably can be done.

> Why would that way be better than pickle?

My suspicion is that UAs would be used more for
storing text and numbers (as seems common for XML files)
than Python data objects.

Does Leo use UAs to store pickles?

I'm sure pickling capability is great, but I'm not convinced
it should be the _default._

No big deal.
.. @+node:ekr.20110415173840.6098: *7* Code related to uA's
.. @+node:ekr.20040701065235.2: *8* putDescendentAttributes
def putDescendentAttributes (self,p):

    nodeIndices = g.app.nodeIndices

    # Create lists of all tnodes whose vnodes are marked or expanded.
    marks = [] ; expanded = []
    for p in p.subtree():
        v = p.v
        if p.isMarked() and p.v not in marks:
            marks.append(v)
        if p.hasChildren() and p.isExpanded() and v not in expanded:
            expanded.append(v)

    result = []
    for theList,tag in ((marks,"marks"),(expanded,"expanded")):
        if theList:
            sList = []
            for v in theList:
                sList.append("%s," % nodeIndices.toString(v.fileIndex))
            s = ''.join(sList)
            # g.trace(tag,[str(p.h) for p in theList])
            result.append('\n%s="%s"' % (tag,s))

    return ''.join(result)
.. @+node:ekr.20080805071954.2: *8* putDescendentVnodeUas
def putDescendentVnodeUas (self,p):

    '''Return the a uA field for descendent vnode attributes,
    suitable for reconstituting uA's for anonymous vnodes.'''

    trace = False
    if trace: g.trace(p.h)

    # Create aList of tuples (p,v) having a valid unknownAttributes dict.
    # Create dictionary: keys are vnodes, values are corresonding archived positions.
    pDict = {} ; aList = []
    for p2 in p.self_and_subtree():
        if hasattr(p2.v,"unknownAttributes"):
            aList.append((p2.copy(),p2.v),)
            pDict[p2.v] = p2.archivedPosition(root_p=p)

    # Create aList of pairs (v,d) where d contains only pickleable entries.
    if aList: aList = self.createUaList(aList)
    if not aList: return ''

    # Create d, an enclosing dict to hold all the inner dicts.
    d = {}
    for v,d2 in aList:
        aList2 = [str(z) for z in pDict.get(v)]
        # g.trace(aList2)
        key = '.'.join(aList2)
        d[key]=d2

    if trace: g.trace(p.h,g.dictToString(d))

    # Pickle and hexlify d
    return d and self.pickle(
        torv=p.v,val=d,tag='descendentVnodeUnknownAttributes') or ''
.. @+node:EKR.20040526202501: *8* putUnknownAttributes
def putUnknownAttributes (self,torv):

    """Put pickleable values for all keys in torv.unknownAttributes dictionary."""

    attrDict = torv.unknownAttributes
    if type(attrDict) != type({}):
        g.es("ignoring non-dictionary unknownAttributes for",torv,color="blue")
        return ''
    else:
        val = ''.join([self.putUaHelper(torv,key,val) for key,val in attrDict.items()])
        # g.trace(torv,attrDict)
        return val
.. @+node:ekr.20090130114732.6: *8* v.u Property
def __get_u(self):
    v = self
    if not hasattr(v,'unknownAttributes'):
        v.unknownAttributes = {}
    return v.unknownAttributes

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

u = property(
    __get_u, __set_u,
    doc = "vnode unknownAttribute property")
.. @+node:ekr.20120205022040.15412: *5* Refactor the key code (not urgent)
@nocolor-node
    
* Define k factory methods:
    
    k.makeKeyStroke(user_setting_string)
    k.makeKeyStrokeFromData(data)
    k.makeShortcutInfo(...)

* Refactor the Qt input code so it calls k.makeKeyStrokeFromData(data).
  This will require untangling the input code from event handling code.
.. @+node:ekr.20110605121601.18847: *6* create_key_event (leoGui)
def create_key_event (self,c,char,stroke,w,x=None,y=None,x_root=None,y_root=None):
    
    # Do not call strokeFromSetting here!
    # For example, this would wrongly convert Ctrl-C to Ctrl-c,
    # in effect, converting a user binding from Ctrl-Shift-C to Ctrl-C.

    return leoKeyEvent(c,char,stroke,w,x,y,x_root,y_root)
.. @+node:ekr.20110605121601.18537: *6* class leoQtEventFilter
class leoQtEventFilter(QtCore.QObject):

    << about internal bindings >>

    @others
.. @+node:ekr.20110605121601.18538: *7* << about internal bindings >>
@nocolor-node
@

Here are the rules for translating key bindings (in leoSettings.leo) into keys
for k.bindingsDict:

1.  The case of plain letters is significant:  a is not A.

2. The Shift- prefix can be applied *only* to letters. Leo will ignore (with a
warning) the shift prefix applied to any other binding, e.g., Ctrl-Shift-(

3. The case of letters prefixed by Ctrl-, Alt-, Key- or Shift- is *not*
significant. Thus, the Shift- prefix is required if you want an upper-case
letter (with the exception of 'bare' uppercase letters.)

The following table illustrates these rules. In each row, the first entry is the
key (for k.bindingsDict) and the other entries are equivalents that the user may
specify in leoSettings.leo:

a, Key-a, Key-A
A, Shift-A
Alt-a, Alt-A
Alt-A, Alt-Shift-a, Alt-Shift-A
Ctrl-a, Ctrl-A
Ctrl-A, Ctrl-Shift-a, Ctrl-Shift-A
!, Key-!,Key-exclam,exclam

This table is consistent with how Leo already works (because it is consistent
with Tk's key-event specifiers). It is also, I think, the least confusing set of
rules.
.. @+node:ekr.20110605121601.18539: *7*  ctor (leoQtEventFilter)
def __init__(self,c,w,tag=''):

    # g.trace('leoQtEventFilter',tag,w)

    # Init the base class.
    QtCore.QObject.__init__(self)

    self.c = c
    self.w = w      # A leoQtX object, *not* a Qt object.
    self.tag = tag

    # Debugging.
    self.keyIsActive = False

    # Pretend there is a binding for these characters.
    close_flashers = c.config.getString('close_flash_brackets') or ''
    open_flashers  = c.config.getString('open_flash_brackets') or ''
    self.flashers = open_flashers + close_flashers
    
    # Support for ctagscompleter.py plugin.
    self.ctagscompleter_active = False
    self.ctagscompleter_onKey = None
.. @+node:ekr.20110605121601.18540: *7* eventFilter
def eventFilter(self, obj, event):

    trace = (False or g.trace_masterKeyHandler) and not g.unitTesting
    verbose = True
    traceEvent = False # True: call self.traceEvent.
    traceKey = (True or g.trace_masterKeyHandler)
    c = self.c ; k = c.k
    eventType = event.type()
    ev = QtCore.QEvent
    gui = g.app.gui
    aList = []

    kinds = [ev.ShortcutOverride,ev.KeyPress,ev.KeyRelease]

    # Hack: QLineEdit generates ev.KeyRelease only on Windows,Ubuntu
    lineEditKeyKinds = [ev.KeyPress,ev.KeyRelease]

    # Important:
    # QLineEdit: ignore all key events except keyRelease events.
    # QTextEdit: ignore all key events except keyPress events.
    if eventType in lineEditKeyKinds:
        p = c.currentPosition()
        isEditWidget = obj == c.frame.tree.edit_widget(p)
        self.keyIsActive = g.choose(
            isEditWidget,
            eventType == ev.KeyRelease,
            eventType == ev.KeyPress)
    else:
        self.keyIsActive = False

    if eventType == ev.WindowActivate:
        gui.onActivateEvent(event,c,obj,self.tag)
        override = False ; tkKey = None
    elif eventType == ev.WindowDeactivate:
        gui.onDeactivateEvent(event,c,obj,self.tag)
        override = False ; tkKey = None
    elif eventType in kinds:
        tkKey,ch,ignore = self.toTkKey(event)
        aList = c.k.masterGuiBindingsDict.get('<%s>' %tkKey,[])
        # g.trace('instate',k.inState(),'tkKey',tkKey,'ignore',ignore,'len(aList)',len(aList))
        if ignore:
            override = False
        # This is extremely bad.
        # At present, it is needed to handle tab properly.
        elif self.isSpecialOverride(tkKey,ch):
            override = True
        elif k.inState():
            override = not ignore # allow all keystrokes.
        else:
            override = len(aList) > 0
    else:
        override = False ; tkKey = '<no key>'
        if self.tag == 'body':
            if eventType == ev.FocusIn:
                g.app.gui.add_border(c,obj)
                c.frame.body.onFocusIn(obj)
            elif eventType == ev.FocusOut:
                g.app.gui.remove_border(c,obj)
                c.frame.body.onFocusOut(obj)
        if self.tag in ('tree','log'):
            if eventType == ev.FocusIn:
                g.app.gui.add_border(c,obj)
            elif eventType == ev.FocusOut:
                g.app.gui.remove_border(c,obj)

    if self.keyIsActive:
        shortcut = self.toStroke(tkKey,ch) # ch is unused.

        if override:
            # Essentially *all* keys get passed to masterKeyHandler.
            if trace and traceKey:
                g.trace('ignore',ignore,'bound',repr(shortcut),repr(aList))
            w = self.w # Pass the wrapper class, not the wrapped widget.
            event = self.create_key_event(event,c,w,ch,tkKey,shortcut)
            ret = k.masterKeyHandler(event)
            c.outerUpdate()
        else:
            if trace and traceKey and verbose:
                g.trace(self.tag,'unbound',tkKey,shortcut)
        
        if trace and traceEvent:
            # Trace key events.
            self.traceEvent(obj,event,tkKey,override)

    elif trace and traceEvent:
        # Trace non-key events.
        self.traceEvent(obj,event,tkKey,override)

    return override
.. @+node:ekr.20110605195119.16937: *7* create_key_event (leoQtEventFilter)
def create_key_event (self,event,c,w,ch,tkKey,shortcut):

    trace = True and not g.unitTesting ; verbose = False
    
    if trace and verbose: g.trace('ch: %s, tkKey: %s, shortcut: %s' % (
        repr(ch),repr(tkKey),repr(shortcut)))
        
    # Last-minute adjustments...
    if shortcut == 'Return':
        ch = '\n' # Somehow Qt wants to return '\r'.
    elif shortcut == 'Escape':
        ch = 'Escape'

    # Switch the Shift modifier to handle the cap-lock key.
    if len(ch) == 1 and len(shortcut) == 1 and ch.isalpha() and shortcut.isalpha():
        if ch != shortcut:
            if trace and verbose: g.trace('caps-lock')
            shortcut = ch

    # Patch provided by resi147.
    # See the thread: special characters in MacOSX, like '@'.
    if sys.platform.startswith('darwin'):
        darwinmap = {
            'Alt-Key-5': '[',
            'Alt-Key-6': ']',
            'Alt-Key-7': '|',
            'Alt-slash': '\\',
            'Alt-Key-8': '{',
            'Alt-Key-9': '}',
            'Alt-e': '€',
            'Alt-l': '@',
        }
        if tkKey in darwinmap:
            shortcut = darwinmap[tkKey]
            
    char = ch
    # Auxiliary info.
    x      = hasattr(event,'x') and event.x or 0
    y      = hasattr(event,'y') and event.y or 0
    # Support for fastGotoNode plugin
    x_root = hasattr(event,'x_root') and event.x_root or 0
    y_root = hasattr(event,'y_root') and event.y_root or 0
    
    if trace and verbose: g.trace('ch: %s, shortcut: %s printable: %s' % (
        repr(ch),repr(shortcut),ch in string.printable))
                
    return leoGui.leoKeyEvent(c,char,shortcut,w,x,y,x_root,y_root)
.. @+node:ekr.20120204061120.10088: *7* Key construction...
.. @+node:ekr.20110605121601.18543: *8* toTkKey & helpers (must not change!)
def toTkKey (self,event):
    
    '''Return tkKey,ch,ignore:
        
    tkKey: the Tk spelling of the event used to look up
           bindings in k.masterGuiBindingsDict.
           **This must not ever change!**
           
    ch:    the insertable key, or ''.
    
    ignore: True if the key should be ignored.
            This is **not** the same as 'not ch'.
    '''

    mods = self.qtMods(event)

    keynum,text,toString,ch = self.qtKey(event)

    tkKey,ch,ignore = self.tkKey(
        event,mods,keynum,text,toString,ch)

    return tkKey,ch,ignore
.. @+node:ekr.20110605121601.18546: *9* tkKey & helper
def tkKey (self,event,mods,keynum,text,toString,ch):

    '''Carefully convert the Qt key to a 
    Tk-style binding compatible with Leo's core
    binding dictionaries.'''

    trace = False and not g.unitTesting
    ch1 = ch # For tracing.
    use_shift = (
        'Home','End','Tab',
        'Up','Down','Left','Right',
        'Next','Prior', # 2010/01/10: Allow Shift-PageUp and Shift-PageDn.
        # 2011/05/17: Fix bug 681797.
        # There is nothing 'dubious' about these provided that they are bound.
        # If they are not bound, then weird characters will be inserted.
        'Delete','Ins','Backspace',
        'F1','F2','F3','F4','F5','F6','F7','F8','F9','F10','F11','F12',
    )

    # Convert '&' to 'ampersand', etc.
    # *Do* allow shift-bracketleft, etc.
    ch2 = self.char2tkName(ch or toString)
    if ch2: ch = ch2 
    if not ch: ch = ''

    if 'Shift' in mods:
        if trace: g.trace(repr(ch))
        if len(ch) == 1 and ch.isalpha():
            mods.remove('Shift')
            ch = ch.upper()
        elif len(ch) > 1 and ch not in use_shift:
            # Experimental!
            mods.remove('Shift')
        # 2009/12/19: Speculative.
        # if ch in ('parenright','parenleft','braceright','braceleft'):
            # mods.remove('Shift')
    elif len(ch) == 1:
        ch = ch.lower()

    if ('Alt' in mods or 'Control' in mods) and ch and ch in string.digits:
        mods.append('Key')

    # *Do* allow bare mod keys, so they won't be passed on.
    tkKey = '%s%s%s' % ('-'.join(mods),mods and '-' or '',ch)

    if trace: g.trace(
        'text: %s toString: %s ch1: %s ch: %s' % (
        repr(text),repr(toString),repr(ch1),repr(ch)))

    ignore = not ch # Essential
    ch = text or toString
    return tkKey,ch,ignore
.. @+node:ekr.20110605121601.18547: *10* char2tkName
char2tkNameDict = {
    # Part 1: same as g.app.guiBindNamesDict
    "&" : "ampersand",
    "^" : "asciicircum",
    "~" : "asciitilde",
    "*" : "asterisk",
    "@" : "at",
    "\\": "backslash",
    "|" : "bar",
    "{" : "braceleft",
    "}" : "braceright",
    "[" : "bracketleft",
    "]" : "bracketright",
    ":" : "colon",  
    "," : "comma",
    "$" : "dollar",
    "=" : "equal",
    "!" : "exclam",
    ">" : "greater",
    "<" : "less",
    "-" : "minus",
    "#" : "numbersign",
    '"' : "quotedbl",
    "'" : "quoteright",
    "(" : "parenleft",
    ")" : "parenright", 
    "%" : "percent",
    "." : "period",     
    "+" : "plus",
    "?" : "question",
    "`" : "quoteleft",
    ";" : "semicolon",
    "/" : "slash",
    " " : "space",      
    "_" : "underscore",
    # Part 2: special Qt translations.
    'Backspace':'BackSpace',
    'Backtab':  'Tab', # The shift mod will convert to 'Shift+Tab',
    'Esc':      'Escape',
    'Del':      'Delete',
    'Ins':      'Insert', # was 'Return',
    # Comment these out to pass the key to the QTextWidget.
    # Use these to enable Leo's page-up/down commands.
    'PgDown':    'Next',
    'PgUp':      'Prior',
    # New entries.  These simplify code.
    'Down':'Down','Left':'Left','Right':'Right','Up':'Up',
    'End':'End',
    'F1':'F1','F2':'F2','F3':'F3','F4':'F4','F5':'F5',
    'F6':'F6','F7':'F7','F8':'F8','F9':'F9',
    'F10':'F10','F11':'F11','F12':'F12',
    'Home':'Home',
    # 'Insert':'Insert',
    'Return':'Return',
    'Tab':'Tab',
    # 'Tab':'\t', # A hack for QLineEdit.
    # Unused: Break, Caps_Lock,Linefeed,Num_lock
}

# Called only by tkKey.

def char2tkName (self,ch):
    val = self.char2tkNameDict.get(ch)
    # g.trace(repr(ch),repr(val))
    return val
.. @+node:ekr.20120204061120.10087: *8* Common key construction helpers
.. @+node:ekr.20110605121601.18541: *9* isSpecialOverride
def isSpecialOverride (self,tkKey,ch):

    '''Return True if tkKey is a special Tk key name.
    '''

    return tkKey or ch in self.flashers
.. @+node:ekr.20110605121601.18542: *9* toStroke (leoQtEventFilter)
def toStroke (self,tkKey,ch):  # ch is unused
    
    '''Convert the official tkKey name to a stroke.'''

    trace = False and not g.unitTesting
    k = self.c.k ; s = tkKey

    table = (
        ('Alt-','Alt+'),
        ('Ctrl-','Ctrl+'),
        ('Control-','Ctrl+'),
        # Use Alt+Key-1, etc.  Sheesh.
        # ('Key-','Key+'),
        ('Shift-','Shift+')
    )
    for a,b in table:
        s = s.replace(a,b)

    if trace: g.trace('tkKey',tkKey,'-->',s)
    return s
.. @+node:ekr.20110605121601.18544: *9* qtKey
def qtKey (self,event):

    '''Return the components of a Qt key event.

    Modifiers are handled separately.
    
    Return keynum,text,toString,ch
    
    keynum: event.key()
    ch:     g.u(chr(keynum)) or '' if there is an exception.
    toString:
        For special keys: made-up spelling that become part of the setting.
        For all others:   QtGui.QKeySequence(keynum).toString()
    text:   event.text()
    '''

    trace = False and not g.unitTesting
    keynum = event.key()
    text   = event.text() # This is the unicode text.

    qt = QtCore.Qt
    d = {
        qt.Key_Shift:   'Key_Shift',
        qt.Key_Control: 'Key_Control',  # MacOS: Command key
        qt.Key_Meta:	'Key_Meta',     # MacOS: Control key, Alt-Key on Microsoft keyboard on MacOs.
        qt.Key_Alt:	    'Key_Alt',	 
        qt.Key_AltGr:	'Key_AltGr',
            # On Windows, when the KeyDown event for this key is sent,
            # the Ctrl+Alt modifiers are also set.
    }

    if d.get(keynum):
        toString = d.get(keynum)
    else:
        toString = QtGui.QKeySequence(keynum).toString()

    try:
        ch1 = chr(keynum)
    except ValueError:
        ch1 = ''

    try:
        ch = g.u(ch1)
    except UnicodeError:
        ch = ch1

    text     = g.u(text)
    toString = g.u(toString)

    if trace and self.keyIsActive:
        mods = '+'.join(self.qtMods(event))
        g.trace(
            'keynum %7x ch %3s toString %s %s' % (
            keynum,repr(ch),mods,repr(toString)))

    return keynum,text,toString,ch
.. @+node:ekr.20120204061120.10084: *9* qtMods
def qtMods (self,event):

    '''Return the text version of the modifiers of the key event.'''

    modifiers = event.modifiers()

    # The order of this table must match the order created by k.strokeFromSetting.
    # When g.new_keys is True, k.strokeFromSetting will canonicalize the setting.

    qt = QtCore.Qt
    
    if sys.platform.startswith('darwin'):
        # Yet another MacOS hack:
        table = (
            (qt.AltModifier,     'Alt'), # For Apple keyboard.
            (qt.MetaModifier,    'Alt'), # For Microsoft keyboard.
            (qt.ControlModifier, 'Control'),
            # No way to generate Meta.
            (qt.ShiftModifier,   'Shift'),
        )
        
    else:
        table = (
            (qt.AltModifier,     'Alt'),
            (qt.ControlModifier, 'Control'),
            (qt.MetaModifier,    'Meta'),
            (qt.ShiftModifier,   'Shift'),
        )

    mods = [b for a,b in table if (modifiers & a)]
    return mods
.. @+node:ekr.20110605121601.18548: *7* traceEvent
def traceEvent (self,obj,event,tkKey,override):

    if g.unitTesting: return
    
    traceFocus = False
    traceKey   = True
    traceLayout = False
    traceMouse = False
    
    c,e = self.c,QtCore.QEvent
    eventType = event.type()
    
    show = []
    
    ignore = [
        e.MetaCall, # 43
        e.Timer, # 1
        e.ToolTip, # 110
    ]
    
    focus_events = (
        (e.Enter,'enter'),
        (e.Leave,'leave'),
        (e.FocusIn,'focus-in'),
        (e.FocusOut,'focus-out'),
        (e.Hide,'hide'), # 18
        (e.HideToParent, 'hide-to-parent'), # 27
        (e.HoverEnter, 'hover-enter'), # 127
        (e.HoverLeave,'hover-leave'), # 128
        (e.HoverMove,'hover-move'), # 129
        (e.Show,'show'), # 17
        (e.ShowToParent,'show-to-parent'), # 26
        (e.WindowActivate,'window-activate'), # 24
        (e.WindowBlocked,'window-blocked'), # 103
        (e.WindowUnblocked,'window-unblocked'), # 104
        (e.WindowDeactivate,'window-deactivate'), # 25
    )
    key_events = (
        (e.KeyPress,'key-press'),
        (e.KeyRelease,'key-release'),
        (e.ShortcutOverride,'shortcut-override'),
    )
    layout_events = (
        (e.ChildPolished,'child-polished'), # 69
        #(e.CloseSoftwareInputPanel,'close-sip'), # 200
            # Event does not exist on MacOS.
        (e.ChildAdded,'child-added'), # 68
        (e.DynamicPropertyChange,'dynamic-property-change'), # 170
        (e.FontChange,'font-change'),# 97
        (e.LayoutRequest,'layout-request'),
        (e.Move,'move'), # 13 widget's position changed.
        (e.PaletteChange,'palette-change'),# 39
        (e.ParentChange,'parent-change'), # 21
        (e.Paint,'paint'), # 12
        (e.Polish,'polish'), # 75
        (e.PolishRequest,'polish-request'), # 74
        # (e.RequestSoftwareInputPanel,'sip'), # 199
            # Event does not exist on MacOS.
        (e.Resize,'resize'), # 14
        (e.StyleChange,'style-change'), # 100
        (e.ZOrderChange,'z-order-change'), # 126
    )
    mouse_events = (
        (e.MouseMove,'mouse-move'), # 155
        (e.MouseButtonPress,'mouse-press'), # 2
        (e.MouseButtonRelease,'mouse-release'), # 3
        (e.Wheel,'mouse-wheel'), # 31
    )
    
    option_table = (
        (traceFocus,focus_events),
        (traceKey,key_events),
        (traceLayout,layout_events),
        (traceMouse,mouse_events),
    )
    
    for option,table in option_table:
        if option:
            show.extend(table)
        else:
            for n,tag in table:
                ignore.append(n)

    for val,kind in show:
        if eventType == val:
            g.trace(
                '%5s %18s in-state: %5s key: %s override: %s: obj: %s' % (
                self.tag,kind,repr(c.k and c.k.inState()),tkKey,override,obj))
            return

    if eventType not in ignore:
        g.trace('%3s:%s obj:%s' % (eventType,'unknown',obj))
.. @+node:ekr.20110605195119.16937: *6* create_key_event (leoQtEventFilter)
def create_key_event (self,event,c,w,ch,tkKey,shortcut):

    trace = True and not g.unitTesting ; verbose = False
    
    if trace and verbose: g.trace('ch: %s, tkKey: %s, shortcut: %s' % (
        repr(ch),repr(tkKey),repr(shortcut)))
        
    # Last-minute adjustments...
    if shortcut == 'Return':
        ch = '\n' # Somehow Qt wants to return '\r'.
    elif shortcut == 'Escape':
        ch = 'Escape'

    # Switch the Shift modifier to handle the cap-lock key.
    if len(ch) == 1 and len(shortcut) == 1 and ch.isalpha() and shortcut.isalpha():
        if ch != shortcut:
            if trace and verbose: g.trace('caps-lock')
            shortcut = ch

    # Patch provided by resi147.
    # See the thread: special characters in MacOSX, like '@'.
    if sys.platform.startswith('darwin'):
        darwinmap = {
            'Alt-Key-5': '[',
            'Alt-Key-6': ']',
            'Alt-Key-7': '|',
            'Alt-slash': '\\',
            'Alt-Key-8': '{',
            'Alt-Key-9': '}',
            'Alt-e': '€',
            'Alt-l': '@',
        }
        if tkKey in darwinmap:
            shortcut = darwinmap[tkKey]
            
    char = ch
    # Auxiliary info.
    x      = hasattr(event,'x') and event.x or 0
    y      = hasattr(event,'y') and event.y or 0
    # Support for fastGotoNode plugin
    x_root = hasattr(event,'x_root') and event.x_root or 0
    y_root = hasattr(event,'y_root') and event.y_root or 0
    
    if trace and verbose: g.trace('ch: %s, shortcut: %s printable: %s' % (
        repr(ch),repr(shortcut),ch in string.printable))
                
    return leoGui.leoKeyEvent(c,char,shortcut,w,x,y,x_root,y_root)
.. @+node:ekr.20070228160107: *6* class leoKeyEvent (leoGui.py)
class leoKeyEvent:

    '''A gui-independent wrapper for gui events.'''
    
    @others

    def __repr__ (self):

        return 'leoKeyEvent: stroke: %s, char: %s, w: %s' % (
            repr(self.stroke),repr(self.char),repr(self.w))
            
    def type(self):
        return 'leoKeyEvent'
.. @+node:ekr.20110605121601.18846: *7* ctor (leoKeyEvent)
def __init__ (self,c,char,shortcut,w,x,y,x_root,y_root):
    
    trace = False and not g.unitTesting
    k = c.k
    
    if g.isStroke(shortcut):
        g.trace('***** (leoKeyEvent) oops: already a stroke',shortcut,g.callers())
        stroke = shortcut
    else:
        stroke = g.KeyStroke(shortcut) if shortcut else None

    assert g.isStrokeOrNone(stroke),'(leoKeyEvent) %s %s' % (
        repr(stroke),g.callers())
        
    if trace: g.trace('(leoKeyEvent) stroke',stroke)
    
    self.c = c
    self.char = char or ''
    self.stroke = stroke
    self.w = self.widget = w
    
    # Optional ivars
    self.x = x
    self.y = y

    # Support for fastGotoNode plugin
    self.x_root = x_root
    self.y_root = y_root
.. @+node:ekr.20110531190516.19365: *4* Maybe
@language rest

.. @+node:ekr.20110611043506.16494: *5*  Ashland sprint items: 3
.. @+node:ekr.20110527084258.18378: *6* New file format
@nocolor-node

** remove window state, expansion status etc.
   stuff from .leo file, and move that over to c.db

- <attr> solves pickle problem.

* Treat gnx's as strings: don't parse them.
  http://mail.google.com/mail/#inbox/12f3d4950fbabeea
  
* Don't save expansion bits in uA if not saving expansion bits. It's illogical
  to save bits in uA's if they aren't save in in the <v> elements.
  
    @bool put_expansion_bits_in_leo_files = False

- Use uuid's?

- Remove spaces from user names.

.. @+node:ekr.20110609042343.16546: *7* Notes
.. @+node:ekr.20110609042343.16548: *8* File format, v3 draft 5
@language rest
@pagewidth 65

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

Leo's file format: version 3, draft 5

This draft is intended to reflect minimal changes from Leo's
present file format, but with improvements mentioned at the
Ashland sprint and developed afterward.

This draft covers only Leo's xml format, but it may be adapted
for use as a json file format.

This draft contains significant invention by EKR. See the next
section. Don't panic: it can be changed :-)

Summary of changes from draft 4
===============================

- Elements will be renamed as follows::

    old         new
    ====        ====
    <vnodes>    <directed-acyclic-graph>
    <tnodes>    <data-list>
    <v>         <node>
    <t>         <data>

- Nesting of <node> elements represents the structure of the DAG,
  just as nesting of <v> elements does at present.
  
- Thus, there will be no <edge> elements.

- Headlines will move from <v> elements to <data> elements.
  This "normalizes" the data: headlines will appear only once.
  
- <attr> elements will represent uA's.  A full discussion below.

  Ideally, I would like to support only string and json formats
  for <attr> elements.  This is open to discussion. 

- Only <node> elements will contain <attr> elements.

- <node> elements for @file nodes will contain
  <at-file-attributes> elements, representing Leo's "hidden
  machinery" attributes.  <at-file-attributes> will contain
  <attr> elements. 

First lines
===========

Leo file will start with the following::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3"
        xmlns:leo="http://www.leo-editor.org/2011/leo"/>
        

No session data
===============

There will be no <globals>, <preferences> or
<find_panel_settings> elements. All such "session data" will be
restored from the cache, or from defaults if caching is disabled.

**Important**: there is no representation of expansion state or
the currently selected node anywhere in the .leo file.
Eliminating these data is contingent on having Leo work well with
caching disabled.

Note: marks are more than session data. They must appear within
<node> elements.


Summary of format
=================

.leo files will have the following form, with similar indentation::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3" xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <directed-acyclic-graph>
        <node id="gnx">
            <!-- contained node elements, if any.
        </node>
        <node id="gnx">
            <!-- contained v elements, if any.
        </node>
        ...
    </directed-acyclic-graph>
    <data-list>
        <data id="gnx">
            <!-- marked attribute appears only if the tnode/vnode is marked -->
            <head>headline text</head>
            <attr key="marked">1</attr>
            <!-- uA's... -->
            <!-- format="string" is the default -->
            <attr key="a">a string</attr>
            <attr key="b" format="json">a json string</attr>
            <attr key="c" format="pickle">a pickled string</attr>
            <attr key="d" format="binhex">a binhexed string</attr>
            ...
            <body>body text</body>
        </data>
        ...
    </data-list>
    </leo_file>

<attr> elements
===============

<attr> elements will one of the following forms::

    <attr key="a">a unicode string</attr>
    <attr key="b" format="json">a json string</attr>
    <attr key="c" format="pickle">a json string</attr>
    <attr key="d" format="binhex">a binhexed string</attr>
    
The value will be a string by default.

If possible, I would like to support only the string and json
formats. This would make the data as transparent as possible.
Please mentally amend the following discussion...

uA's that start with "binhex_" will use the binhex format. This
prefix must be retained in the type field, so the read code can
restore them.

If the value is not a string, and there is no "binhex_" prefix,
the write code will use format="json" if json.dumps succeeds, and
will use format="pickle" otherwise.

No <attr> element will be written if both json.dumps and
pickle.dumps fail. Attribute failures will create a warning for
the plugin developer.
    
Descendant attributes in @file trees
====================================

Descendants of @file nodes do not appear in .leo files. Thus,
important data must be stored in the so-called hidden machinery:
attributes of the @file node.

The <at-file-attributes> element may optionally be contained in
the <node> element for @file nodes::

    <at-file-attributes>
        <attr>key="ua-name"
            format="(empty)/json/pickle/binhex"
            gnx="gnx">value
        </attr>
        ...
    </at-file-attributes>
.. @+node:ekr.20110421132230.6107: *8* File format, v3 draft 4
@language rest
@pagewidth 65

Leo's file format: version 3, draft 4

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

Here is the latest version, with the graphml stuff removed.

This draft is intended to reflect our recent discussions, with no
new invention from me. All comments and corrections welcome.

This draft covers only Leo's xml format, but it may be adapted
for use as a json file format.

Recent changes
==============

- Removed graphml stuff, including leo: prefixes.

- Used "key" in <attr> elements (and "type" in <edge> elements.)

First lines
===========

Leo file will start with the following::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3"
        xmlns:leo="http://www.leo-editor.org/2011/leo"/>
        

No session data
===============

There will be no <globals>, <preferences> or
<find_panel_settings> elements. All such "session data" will be
restored from the cache, or from defaults if caching is disabled.

**Important**: there is no representation of expansion state or
the currently selected node anywhere in the .leo file.
Eliminating these data is contingent on having Leo work well with
caching disabled.

Note: marks are more than session data. They must appear
somewhere within <node> elements.


Summary of format
=================

.leo files will have the following form, with similar indentation::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo_file file_format="3" xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <graph>
    <nodes>
        <!-- marked attribute appears only if the vnode is marked -->
        <node id="gnx"> 
            <head>headline text</head>
            <attr key="marked">1</attr>
            <!-- uA's... -->
            <!-- format="string" is the default -->
            <attr key="a">a string</attr>
            <attr key="b" format="json">a json string</attr>
            <attr key="c" format="pickle">a pickled string</attr>
            <attr key="d" format="binhex">a binhexed string</attr>
            ...
            <body>body text</body>
        </node>
        ...
        <!-- @file nodes contain a <data> package -->
        <node id="gnx">
            <head>@file x.py</head>
            ...
            <at-file-attributes>
                <attr>key="ua-name"
                    format="(empty)/json/pickle/binhex"
                    gnx="gnx">value
                </attr>
                ...
            </at-file-attributes>
        </node>
        ...
    </nodes>
    <edges>
        <edge type="child" from="gnx" to="gnx"</edge>
        ...
    </edges>
    </graph>
    </leo_file>

<attr> elements
===============

<attr> elements will one of the following forms::

    <attr key="a">a unicode string</attr>
    <attr key="b" format="json">a json string</attr>
    <attr key="c" format="pickle">a json string</attr>
    <attr key="d" format="binhex">a binhexed string</attr>
    
That is, the value will be a string by default.

uA's that start with "binhex_" will use the binhex format. This
prefix must be retained in the type field, so the read code can
restore them.

If the value is not a string, and there is no "binhex_" prefix,
the write code will use format="json" if json.dumps succeeds, and
will use format="pickle" otherwise.

No <attr> element will be written if both json.dumps and
pickle.dumps fail. Attribute failures will create a warning for
the plugin developer.

<edge> elements
===============

<edge> elements will have the form::

    <edge type="child" from="gnx" to="gnx"/>
    
Leo will use type="child" to represent Leo's official edges.
Plugins are free to define any type except "child". Examples::

    <edge type="undirected" from="gnx" to="gnx"/>
    <edge type="bidirectional" from="gnx" to="gnx"/>
    <edge type="backlink" from="gnx" to="gnx"/>
    <edge type="myPlugin" from="gnx" to="gnx"/>
    
Descendant attributes in @file trees
====================================

Descendants of @file nodes do not appear in .leo files. Thus,
important data must be stored in the so-called hidden machinery:
attributes of the @file node.

The <at-file-attributes> element may be contained in the
<node> element for @file nodes::

    <at-file-attributes>
        <attr>key="ua-name"
            format="(empty)/json/pickle/binhex"
            gnx="gnx">value
        </attr>
        ...
    </at-file-attributes>
    
In other words, we use the graphml <attr> element, extended with the
gnx attribute, to represent all the uA's in the descendants of @file nodes.
.. @+node:ekr.20110419083918.6104: *8* File format, v3 graphml
@language rest
@pagewidth 65

This draft is intended to reflect our recent discussions, with no
new invention from me. All comments and corrections welcome.

The draft is also intended to be compatible with graphml.

This draft covers only Leo's xml format, but it may be adapted
for use as a json file format.

I am willing to change "type" to "key" in <edge> elements if that
would be preferable.

Recent changes
==============

- Added <graphml> element defining the default namespace.

- Defined the leo namespace for leo-only elements.
    - Renamed <leo_file> to <leo:outline>
    - Renamed <descendant-attributes> to <leo:at-file-attributes>

- Used <leo:at-file-attributes> for marks, removing the special case.

- Enclosed <leo:descendant-attributes> in a (graphml) <data> element.

- Changed the format of the "marked" attribute to be a string-valued attribute.

First lines
===========

Leo file will start with the following::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo:file file_format="3"
        xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <graphml xmlns="http://graphml.graphdrawing.org/xmlns"/>
        

No session data
===============

There will be no <globals>, <preferences> or
<find_panel_settings> elements. All such "session data" will be
restored from the cache, or from defaults if caching is disabled.

**Important**: there is no representation of expansion state or
the currently selected node anywhere in the .leo file.
Eliminating these data is contingent on having Leo work well with
caching disabled.

Note: marks are more than session data. They must appear
somewhere within <node> elements.


Summary of format
=================

.leo files will have the following form, with similar indentation::

    <?xml version="1.0" encoding="utf-8"?>
    <?xml-stylesheet my_stylesheet?>
    <!-- Created by Leo (http://webpages.charter.net/edreamleo/front.html) -->
    <leo:outline file_format="3" xmlns:leo="http://www.leo-editor.org/2011/leo"/>
    <graphml xmlns="http://graphml.graphdrawing.org/xmlns"/>
    <graph>
    <nodes>
        <!-- marked attribute appears only if the vnode is marked -->
        <node id="gnx"> 
            <head>headline text</head>
            <attr key="marked">1</attr>
            <!-- uA's... -->
            <!-- format="string" is the default -->
            <attr key="a">a string</attr>
            <attr key="b" format="json">a json string</attr>
            <attr key="c" format="pickle">a pickled string</attr>
            <attr key="d" format="binhex">a binhexed string</attr>
            ...
            <body>body text</body>
        </node>
        ...
        <!-- @file nodes contain a <data> package -->
        <node id="gnx">
            <head>@file x.py</head>
            ...
            <data>
                <leo:at-file-attributes>
                    <attr>key="ua-name"
                        format="(empty)/json/pickle/binhex"
                        gnx="gnx">value
                    </attr>
                    ...
                </leo:at-file-attributes>
            </data>
        </node>
        ...
    </nodes>
    <edges>
        <edge type="child" from="gnx" to="gnx"</edge>
        ...
    </edges>
    </graph>
    </graphml>
    </leo:outline>

<attr> elements
===============

<attr> elements will one of the following forms::

    <attr key="a">a unicode string</attr>
    <attr key="b" format="json">a json string</attr>
    <attr key="c" format="pickle">a json string</attr>
    <attr key="d" format="binhex">a binhexed string</attr>
    
That is, the value will be a string by default.

uA's that start with "binhex_" will use the binhex format. This
prefix must be retained in the type field, so the read code can
restore them.

If the value is not a string, and there is no "binhex_" prefix,
the write code will use format="json" if json.dumps succeeds, and
will use format="pickle" otherwise.

No <attr> element will be written if both json.dumps and
pickle.dumps fail. Attribute failures will create a warning for
the plugin developer.

<edge> elements
===============

<edge> elements will have the form::

    <edge type="child" from="gnx" to="gnx"/>
    
Leo will use type="child" to represent Leo's official edges.
Plugins are free to define any type except "child". Examples::

    <edge type="undirected" from="gnx" to="gnx"/>
    <edge type="bidirectional" from="gnx" to="gnx"/>
    <edge type="backlink" from="gnx" to="gnx"/>
    <edge type="myPlugin" from="gnx" to="gnx"/>
    
Descendant attributes in @file trees
====================================

Descendants of @file nodes do not appear in .leo files. Thus,
important data must be stored in the so-called hidden machinery:
attributes of the @file node.

The <leo:at-file-attributes> element may be contained in the
<node> element for @file nodes. For compatibility with graphml,
it will enclosed in a data element::
    
    <data>
        <leo:at-file-attributes>
            <attr>key="ua-name"
                format="(empty)/json/pickle/binhex"
                gnx="gnx">value
            </attr>
            ...
        </leo:at-file-attributes>
    </data>
    
In other words, we use the graphml <attr> element, extended with the
gnx attribute, to represent all the uA's in the descendants of @file nodes.
.. @+node:ekr.20090218115025.3: *8* Why are attributes pickled by default?
@nocolor-node

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

> On Wed, Feb 18, 2009 at 12:12 PM, Kent Tenney <ktenney@gmail.com> wrote:
>>
>> Currently, Leo pickles the value of unknown attributes unless
>> the name starts with 'str_'
>>
>> Running the following code in node 'UA'
>>
>> p = c.currentPosition()
>> p.v.u = {'hello':'world', 'str_hello':'world'}
>>
>> results in the following in the .leo file:
>>
>> <v t="ktenney.20090218114928.367" str_hello="world"
>> hello="5505776f726c6471002e"><vh>UA</vh></v>
>>
>> I think this is surprising, Python-centric and contrary to the
>> spirit of Leo as a flexible data management platform.
>
> I suppose your point is that you can't create an arbitrarily named attribute
> with a string value. Does that create a real problem?

It requires a translation layer, either to (un)munge the name or
(un)pickle. Real problem?

Let's say each time I think 'I can use UAs to store that' I change
my mind when I realize my values will be in a pickle. (I really don't
want to name all my attributes str_xxx)

> As far as being Python-centric, can you suggest any other way of converting
> arbitrary data to a text string?

How is it done in any other XML file?
I've not used XML for arbitrary data, but it probably can be done.

> Why would that way be better than pickle?

My suspicion is that UAs would be used more for
storing text and numbers (as seems common for XML files)
than Python data objects.

Does Leo use UAs to store pickles?

I'm sure pickling capability is great, but I'm not convinced
it should be the _default._

No big deal.
.. @+node:ekr.20110415173840.6098: *7* Code related to uA's
.. @+node:ekr.20040701065235.2: *8* putDescendentAttributes
def putDescendentAttributes (self,p):

    nodeIndices = g.app.nodeIndices

    # Create lists of all tnodes whose vnodes are marked or expanded.
    marks = [] ; expanded = []
    for p in p.subtree():
        v = p.v
        if p.isMarked() and p.v not in marks:
            marks.append(v)
        if p.hasChildren() and p.isExpanded() and v not in expanded:
            expanded.append(v)

    result = []
    for theList,tag in ((marks,"marks"),(expanded,"expanded")):
        if theList:
            sList = []
            for v in theList:
                sList.append("%s," % nodeIndices.toString(v.fileIndex))
            s = ''.join(sList)
            # g.trace(tag,[str(p.h) for p in theList])
            result.append('\n%s="%s"' % (tag,s))

    return ''.join(result)
.. @+node:ekr.20080805071954.2: *8* putDescendentVnodeUas
def putDescendentVnodeUas (self,p):

    '''Return the a uA field for descendent vnode attributes,
    suitable for reconstituting uA's for anonymous vnodes.'''

    trace = False
    if trace: g.trace(p.h)

    # Create aList of tuples (p,v) having a valid unknownAttributes dict.
    # Create dictionary: keys are vnodes, values are corresonding archived positions.
    pDict = {} ; aList = []
    for p2 in p.self_and_subtree():
        if hasattr(p2.v,"unknownAttributes"):
            aList.append((p2.copy(),p2.v),)
            pDict[p2.v] = p2.archivedPosition(root_p=p)

    # Create aList of pairs (v,d) where d contains only pickleable entries.
    if aList: aList = self.createUaList(aList)
    if not aList: return ''

    # Create d, an enclosing dict to hold all the inner dicts.
    d = {}
    for v,d2 in aList:
        aList2 = [str(z) for z in pDict.get(v)]
        # g.trace(aList2)
        key = '.'.join(aList2)
        d[key]=d2

    if trace: g.trace(p.h,g.dictToString(d))

    # Pickle and hexlify d
    return d and self.pickle(
        torv=p.v,val=d,tag='descendentVnodeUnknownAttributes') or ''
.. @+node:EKR.20040526202501: *8* putUnknownAttributes
def putUnknownAttributes (self,torv):

    """Put pickleable values for all keys in torv.unknownAttributes dictionary."""

    attrDict = torv.unknownAttributes
    if type(attrDict) != type({}):
        g.es("ignoring non-dictionary unknownAttributes for",torv,color="blue")
        return ''
    else:
        val = ''.join([self.putUaHelper(torv,key,val) for key,val in attrDict.items()])
        # g.trace(torv,attrDict)
        return val
.. @+node:ekr.20090130114732.6: *8* v.u Property
def __get_u(self):
    v = self
    if not hasattr(v,'unknownAttributes'):
        v.unknownAttributes = {}
    return v.unknownAttributes

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

u = property(
    __get_u, __set_u,
    doc = "vnode unknownAttribute property")
.. @+node:ekr.20110528034751.18273: *6* Global search in Nav plugin?
.. @+node:ekr.20110613110911.16421: *6* Read/write files in json format
.. @+node:ekr.20051110155735.1: *5* Improve Spell tab & spell checker
@nocolor

- Per-pane key bindings. (arrows, etc.)
- Try default fonts for spell buttons.
- Select the first entry.

@color
.. @+node:ekr.20090907080624.6081: *6* Spell checker should check headlines
.. @+node:ekr.20101004092958.5914: *5* Write treepad scanner
@ treepad.py is from the treepad website
.. @+node:ekr.20101004092958.5939: *6* treepad.py
@first #! /usr/local/bin/python

# treepad.py

@language python
@tabwidth -4
@others
if __name__ == '__main__':
    Main().Run()

.. @+node:ekr.20101004092958.5940: *7* treepad declarations
import sys, os, re, string

# constants
VERSION = "<Treepad version 2.7>"

# regexes
END_RE = re.compile(r'^<end node> ([^ ]+)$')
.. @+node:ekr.20101004092958.5941: *7* class Node
class Node:
    @others
.. @+node:ekr.20101004092958.5942: *8* __init__ (Node/treepad)
def __init__(self):
    self.title    = ""
    self.level    = 0
    self.article  = []
    self.children = []
    self.parent   = None
    self.end      = ""
.. @+node:ekr.20101004092958.5943: *8* __str__
def __str__(self):
    return "%s/%d" % (self.title, self.level)
.. @+node:ekr.20101004092958.5944: *8* addchild
def addchild(self, node):
    assert self.level == node.level-1 and node.parent is None
    node.parent = self
    self.children.append( node )
.. @+node:ekr.20101004092958.5945: *8* findparent
def findparent(self, node):
    if self.level == (node.level-1): return self
    return self.parent.findparent(node)
.. @+node:ekr.20101004092958.5946: *8* writenode
def writenode(self, fp):
    fp.write("dt=Text\n")
    fp.write("<node>\n")
    fp.write("%s\n" % self.title)
    fp.write("%s\n" % self.level)
    for line in self.article:
        fp.write("%s\n" % line)
    fp.write("<end node> %s\n" % self.end)
.. @+node:ekr.20101004092958.5947: *8* writetree
def writetree(self, fp):
    self.writenode(fp)
    for node in self.children:
        node.writetree(fp)

.. @+node:ekr.20101004092958.5948: *7* class NodeReader
class NodeReader:
    @others
.. @+node:ekr.20101004092958.5949: *8* __init__ (NodeReader)
def __init__(self, fname, fp):
    self.fname    = fname
    self.fp       = fp
.. @+node:ekr.20101004092958.5950: *8* expect
def expect(self, text, line=None):
    if line is None:
        line = self.fp.readline().strip()
    assert line == text, "expected " + line + " == " + text
.. @+node:ekr.20101004092958.5951: *8* readstart
def readstart(self):
    self.expect(VERSION)
.. @+node:ekr.20101004092958.5952: *8* readnode
def readnode(self):
    line = self.fp.readline()
    if line is None:
        return None
    line = line.strip()
    if len(line) < 1:
        return None
    self.expect("dt=Text", line)
    self.expect("<node>")
    node = Node()
    node.title = self.fp.readline().strip()
    node.level = int(self.fp.readline().strip())
    while 1:
        line = self.fp.readline()
        m = re.match(END_RE, line)
        if m:
            node.end = m.group(1).strip()
            break
        node.article.append( line.strip() )
    return node

.. @+node:ekr.20101004092958.5953: *7* class TreeReader
class TreeReader:
    @others
.. @+node:ekr.20101004092958.5954: *8* __init__(TreeReader)
def __init__(self, fname, fp=None):
    if fp is None: fp = open(fname, 'r')
    self.nodereader = NodeReader(fname, fp)
    self.root = None
    self.prev = None
.. @+node:ekr.20101004092958.5955: *8* add
def add(self, node):
    if self.prev is None:
        assert node.level == 0
        self.root = node
    else:
        assert node.level > 0
        parent = self.prev.findparent(node)
        parent.addchild( node )
    self.prev = node
.. @+node:ekr.20101004092958.5956: *8* read
def read(self):
    self.nodereader.readstart()
    prev = None
    while 1:
        node = self.nodereader.readnode()
        if node is None: break
        self.add(node)

.. @+node:ekr.20101004092958.5957: *7* class TreeWriter
class TreeWriter:
    @others
.. @+node:ekr.20101004092958.5958: *8* __init__ (TreeWriter)
def __init__(self, fname, fp=None):
    if fp is None: fp = open(fname, 'w')
    self.fname = fname
    self.fp    = fp
.. @+node:ekr.20101004092958.5959: *8* write
def write(self, root):
    self.fp.write("%s\n" % VERSION)
    root.writetree(self.fp)

.. @+node:ekr.20101004092958.5960: *7* class Main
class Main:
    @others
.. @+node:ekr.20101004092958.5961: *8* __init__ (Main)
def __init__(self):
    self.infile  = sys.argv[1]
    self.outfile = sys.argv[2]
    self.reader  = TreeReader(self.infile)
    self.writer  = TreeWriter(self.outfile)
.. @+node:ekr.20101004092958.5962: *8* Run

def Run(self):
    self.reader.read()
    self.writer.write(self.reader.root)

.. @+node:ekr.20110520051220.18203: *5* Cross-tab search
@language rest

This would be a substitute for cross-file clones.
.. @+node:ekr.20071001052501: *5* Versioning for nodes
@nocolor

One feature I have not seen in SCS system is something which might be called
"history compression": I might be interested in having both version 5 and 6
in my source tree, when the current version is 7, but I am not really interested
in the 2000 steps which transformed 5 into 6 (just suggested this feature to
the bazaar people). This happens actually quite often to me, since I use the
SCS as a back-up system, saving many (uninteresting) intermediate steps while
implementing a new feature.
.. @+node:ekr.20080802070659.11: *5* Make node attributes visible, and supported by Leo's core
.. @+node:ekr.20110614123640.6587: *5* Add headline/color functions to Leo's core
@nocolor-node

http://groups.google.com/group/leo-editor/browse_thread/thread/7e279fe3dedf42be/f00fde4df5b39ded

What uA should be used to specify node colors?

if the foreground / background color API uses uAs,
would/should the uAs use the reserved "leo_&lt;something&gt;"
namespace?

-------------------

Terry Brown

Sounds like something I may have brought up, long ago.

I was thinking that setting the fore/background color of nodes in the
tree should be a "gui core" function, and that the info should be
stored in uA, and so wanted to know what key should be used in uA for
that.  I think the docs say top level keys starting with "leo_" are
reserved, and probably wanted a ruling on

v.u['leo_fg'] = 'red'

vs

v.u['leo_tree_style']['fg'] = 'red'

etc.

I think the question may be more complicated than just what to call the
key, so you can probably retire the todo item.
.. @+node:ekr.20100112051224.6226: *5* Vim-related: Range prefix to commands/objects (k.getArgs)
The ability to specify a numeric range prefix is not supported. For example,
entering "3dd" will not delete the next three lines and "20G" will not move the
cursor to the 20th line in the file.

The ability to specify a numeric range prefix to an object is not supported. For
example, the "d2fx" command should Delete up to and including the 2nd Found "x"
character.
.. @+node:ekr.20110601105631.19373: *6* Simple vim bindings
.. @+node:ekr.20110601105631.19374: *7* Cursors
Vi normally uses two different "current character" designators depending on the
current state.

Insert state:

In the Insert state, a vertical bar is placed between two characters to indicate
where the next key will be inserted. Leo's cursor is of this type.

Command state: 

In the Command state, vi expects that the cursor is highlighting a current
character and provides commands to enter the insert state or paste text either
before or after that current character. Leo's vi emulation currently does not
support a "current character" cursor. As a result, inserting and pasting before
or after is replaced by inserting or pasting "at" the current cursor location.
For example, the 'i' and 'a' command are both mapped to enter the insert state
at the current cursor location.
.. @+node:ekr.20110601105631.19375: *7* Enter insert mode after ctrl-h
.. @+node:ekr.20110601105631.19376: *7* Colorize headline text depending on state
.. @+node:ekr.20110601105631.19377: *7* colon destroys alt-x binding
This project reorganizes makeBindingFromCommandsDict
.. @+node:ekr.20110518103946.18179: *5* Add external/leosax.py to leoPyRef.leo
http://groups.google.com/group/leo-editor/browse_thread/thread/93f2cc88ebbf9893

Would be ok with you if I pulled it into leoPy.leo, thereby adding sentinels to it? 
.. @+node:ekr.20090801103907.6018: *5* Add entries to global dicts for more languages (waiting for requests)
http://groups.google.com/group/leo-editor/browse_thread/thread/b41ddfeb3c84e780

Especially languages in leo/modes.

** Only c.getOpenWithExt uses app.language_extension_dict.

I'll wait until I get requests for particular language.
.. @+node:ekr.20110528103005.18323: *6* Found: extension_dict
.. @+node:ekr.20080819075811.13: *7* adjustTargetLanguage
def adjustTargetLanguage (self,fn):

    """Use the language implied by fn's extension if
    there is a conflict between it and c.target_language."""

    at = self ; c = at.c

    if c.target_language:
        junk,target_ext = g.os_path_splitext(fn)  
    else:
        target_ext = ''

    junk,ext = g.os_path_splitext(fn)

    if ext:
        if ext.startswith('.'): ext = ext[1:]

        language = g.app.extension_dict.get(ext)
        if language:
            c.target_language = language
        else:
            # An unknown language.
            pass # Use the default language, **not** 'unknown_language'
.. @+node:ekr.20090212054250.9: *7* c.createNodeFromExternalFile
def createNodeFromExternalFile(self,fn):

    '''Read the file into a node.
    Return None, indicating that c.open should set focus.'''

    c = self

    s,e = g.readFileIntoString(fn)
    if s is None: return
    head,ext = g.os_path_splitext(fn)
    if ext.startswith('.'): ext = ext[1:]
    language = g.app.extension_dict.get(ext)
    if language:
        prefix = '@color\n@language %s\n\n' % language
    else:
        prefix = '@killcolor\n\n'
    p2 = c.insertHeadline(op_name='Open File', as_child=False)
    p2.h = '@edit %s' % fn # g.shortFileName(fn)
    p2.b = prefix + s
    w = c.frame.body.bodyCtrl
    if w: w.setInsertPoint(0)
    c.redraw()
    c.recolor()
.. @+node:ekr.20031218072017.2824: *7* c.getOpenWithExt
def getOpenWithExt (self,p,ext):

    trace = False and not g.app.unitTesting
    if trace: g.trace(ext)
    
    c = self
    
    if ext:
        for ch in ("'",'"'):
            if ext.startswith(ch): ext = ext.strip(ch)

    if not ext:
        # if node is part of @<file> tree, get ext from file name
        for p2 in p.self_and_parents():
            if p2.isAnyAtFileNode():
                fn = p2.h.split(None,1)[1]
                ext = g.os_path_splitext(fn)[1]
                if trace: g.trace('found node:',ext,p2.h)
                break

    if not ext:
        theDict = c.scanAllDirectives()
        language = theDict.get('language')
        ext = g.app.language_extension_dict.get(language)
        if trace: g.trace('found directive',language,ext)

    if not ext:
        ext = '.txt'
        if trace: g.trace('use default (.txt)')

    if ext[0] != '.':
        ext = '.'+ext
        
    if trace: g.trace(ext)

    return ext
.. @+node:EKR.20040504150046.4: *7* g.comment_delims_from_extension
def comment_delims_from_extension(filename):

    """
    Return the comment delims corresponding to the filename's extension.

    >>> import leo.core.leoGlobals as g
    >>> g.comment_delims_from_extension(".py")
    ('#', '', '')

    >>> g.comment_delims_from_extension(".c")
    ('//', '/*', '*/')

    >>> g.comment_delims_from_extension(".html")
    ('', '<!--', '-->')

    """

    if filename.startswith('.'):
        # Python 2.6 changes how splitext works.
        root,ext = None,filename
    else:
        root, ext = os.path.splitext(filename)
    if ext == '.tmp':
        root, ext = os.path.splitext(root)

    language = g.app.extension_dict.get(ext[1:])
    if ext:
        return g.set_delims_from_language(language)
    else:
        g.trace("unknown extension: %s, filename: %s, root: %s" % (
            repr(ext),repr(filename),repr(root)))
        return '','',''
.. @+node:ekr.20080811174246.1: *7* languageForExtension
def languageForExtension (self,ext):

    '''Return the language corresponding to the extension ext.'''

    unknown = 'unknown_language'

    if ext.startswith('.'): ext = ext[1:]

    if ext:
        z = g.app.extra_extension_dict.get(ext)
        if z not in (None,'none','None'):
            language = z
        else:
            language = g.app.extension_dict.get(ext)
        if language in (None,'none','None'):
            language = unknown
    else:
        language = unknown

    # g.trace(ext,repr(language))

    # Return the language even if there is no colorizer mode for it.
    return language
.. @+node:ekr.20031218072017.368: *6* << define global data structures >> (leoApp.py)
# Internally, lower case is used for all language names.
self.language_delims_dict = {
    # Keys are languages, values are 1,2 or 3-tuples of delims.
    "ada"           : "--",
    "batch"         : "REM_", # Use the REM hack.
    "actionscript"  : "// /* */", #jason 2003-07-03
    "autohotkey"    : "; /* */", #TL - AutoHotkey language
    "c"             : "// /* */", # C, C++ or objective C.
    "config"        : "#", # Leo 4.5.1
    "csharp"        : "// /* */", # C#
    "cpp"           : "// /* */",# C++.
    "css"           : "/* */", # 4/1/04
    "cweb"          : "@q@ @>", # Use the "cweb hack"
    "cython"        : "#",
    "elisp"         : ";",
    "forth"         : "\\_ _(_ _)", # Use the "REM hack"
    "fortran"       : "C",
    "fortran90"     : "!",
    "haskell"       : "--_ {-_ _-}",
    "haxe"          : "//",
    "html"          : "<!-- -->",
    "ini"           : ";",
    "java"          : "// /* */",
    "javascript"    : "// /* */", # EKR: 2011/11/12: For javascript import test.
    "javaserverpage": "<%-- --%>", # EKR: 2011/11/25
    "kshell"        : "#", # Leo 4.5.1.
    "latex"         : "%",
    "lisp"          : ";", # EKR: 2010/09/29
    "lua"           : "--",  # ddm 13/02/06
    "matlab"        : "%", # EKR: 2011/10/21
    "nsi"           : ";", # EKR: 2010/10/27
    "noweb"         : "%", # EKR: 2009-01-30. Use Latex for doc chunks.
    "pascal"        : "// { }",
    "perl"          : "#",
    "perlpod"       : "# __=pod__ __=cut__", # 9/25/02: The perlpod hack.
    "php"           : "// /* */", # 6/23/07: was "//",
    "plain"         : "#", # We must pick something.
    "plsql"         : "-- /* */", # SQL scripts qt02537 2005-05-27
    "python"        : "#",
    "rapidq"        : "'", # fil 2004-march-11
    "rebol"         : ";",  # jason 2003-07-03
    "rest"          : ".._",
    "rst"           : ".._",
    "ruby"          : "#",  # thyrsus 2008-11-05
    "scala"         : "// /* */",
    "shell"         : "#",  # shell scripts
    "tcltk"         : "#",
    "tex"           : "%", # Bug fix: 2008-1-30: Fixed Mark Edginton's bug.
    "unknown"       : "#", # Set when @comment is seen.
    "unknown_language" : '#--unknown-language--',
        # For unknown extensions in @shadow files.
    "vim"           : "\"",
    "vimoutline"    : "#",  #TL 8/25/08 Vim's outline plugin
    "xml"           : "<!-- -->",
    "xslt"          : "<!-- -->",
}

# Used only by c.getOpenWithExt.
self.language_extension_dict = {
    # Keys are languages, values are extensions.
    "ada"           : "ada",
    "actionscript"  : "as", #jason 2003-07-03
    "autohotkey"    : "ahk", #TL - AutoHotkey language
    "batch"         : "bat", # Leo 4.5.1.
    "c"             : "c",
    "config"        : "cfg",
    "cpp"           : "cpp",
    "css"           : "css", # 4/1/04
    "cweb"          : "w",
    #"cython"        : "pyd",
    #"cython"        : "pyi",
    "cython"        : "pyx", # Only one extension is valid at present.
    "elisp"         : "el",
    "forth"         : "forth",
    "fortran"       : "f",
    "fortran90"     : "f90",
    "haskell"       : "hs",
    "haxe"          : "hx",
    "html"          : "html",
    "ini"           : "ini",
    "java"          : "java",
    "javascript"    : "js", # EKR: 2011/11/12: For javascript import test.
    "javaserverpage": "jsp", # EKR: 2011/11/25
    "kshell"        : "ksh", # Leo 4.5.1.
    "latex"         : "tex", # 1/8/04
    "lua"           : "lua",  # ddm 13/02/06
    "matlab"        : "m", # EKR: 2011/10/21
    "nsi"           : "nsi", # EKR: 2010/10/27
    "noweb"         : "nw",
    "pascal"        : "p",
    "perl"          : "pl",      # 11/7/05
    "perlpod"       : "pod",  # 11/7/05
    "php"           : "php",
    "plain"         : "txt",
    "python"        : "py",
    "plsql"         : "sql", # qt02537 2005-05-27
    "rapidq"        : "bas", # fil 2004-march-11
    "rebol"         : "r",    # jason 2003-07-03
    # "rst"           : "rst", # caught by pylint.
    "rst"           : "rest",
    "ruby"          : "rb",   # thyrsus 2008-11-05
    "scala"         : "scala",
    "shell"         : "sh",   # DS 4/1/04
    "tex"           : "tex",
    "tcltk"         : "tcl",
    "unknown"       : "txt", # Set when @comment is seen.
    "vim"           : "vim",
    "vimoutline"    : "otl",  #TL 8/25/08 Vim's outline plugin
    "xml"           : "xml",
    "xslt"          : "xsl",
}

self.extension_dict = {
    # Keys are extensions, values are languages.
    "ada"   : "ada",
    "adb"   : "ada",
    "ahk"   : "autohotkey",  # EKR: 2009-01-30.
    "as"    : "actionscript",
    "bas"   : "rapidq",
    "bat"   : "batch",
    "c"     : "c",
    "cfg"   : "config",
    "cpp"   : "cpp",
    "css"   : "css",
    "el"    : "elisp",
    "forth" : "forth",
    "f"     : "fortran",
    "f90"   : "fortran90",
    "h"     : "c",
    "html"  : "html",
    "hs"    : "haskell",
    "ini"   : "ini",
    "java"  : "java",
    "js"    : "javascript", # EKR: 2011/11/12: For javascript import test.
    "jsp"   : "javaserverpage", # EKR: 2011/11/25: For @shadow.
    "ksh"   : "kshell", # Leo 4.5.1.
    "lua"   : "lua",  # ddm 13/02/06
    "m"     : "matlab", # EKR 2011/10/21
    "nsi"   : "nsi", # EKR: 2010/10/27
    "nw"    : "noweb",
    "otl"   : "vimoutline",  #TL 8/25/08 Vim's outline plugin
    "p"     : "pascal",
    "pl"    : "perl",   # 11/7/05
    "pod"   : "perlpod", # 11/7/05
    "php"   : "php",
    "py"    : "python",
    "pyd"   : "cython",
    "pyi"   : "cython",
    "pyx"   : "cython",
    "sql"   : "plsql", # qt02537 2005-05-27
    "r"     : "rebol",
    "rb"    : "ruby", # thyrsus 2008-11-05
    "rest"  : "rst",
    "rst"   : "rst",
    "scala" : "scala",
    "sh"    : "shell",
    "tex"   : "tex",
    "txt"   : "plain",
    "tcl"   : "tcltk",
    "vim"   : "vim",
    "w"     : "cweb",
    "xml"   : "xml",
    "xsl"   : "xslt",
    "hx"    : "haxe",
}

# Extra language extensions, used to associate extensions with mode files.
# Used by importCommands.languageForExtension.
# Keys are extensions, values are corresponding mode file (without .py)
# A value of 'none' is a signal to unit tests that no extension file exists.
self.extra_extension_dict = {
    'actionscript': 'actionscript',
    'ada'   : 'ada95',
    'adb'   : 'none', # ada??
    'awk'   : 'awk',
    'bas'   : 'none', # rapidq
    'bat'   : 'none', # batch
    'cfg'   : 'none', # Leo 4.5.1
    'cpp'   : 'c',
    'el'    : 'lisp',
    'f'     : 'fortran90',
    'hx'    : 'none',
    'ksh'   : 'none', # Leo 4.5.1
    'nsi'   : 'none', # Leo 4.8.
    'nw'    : 'none', # noweb.
    'otl'   : 'none', # vimoutline.
    'pod'   : 'perl',
    'tcl'   : 'tcl',
    'unknown_language': 'none',
    'w'     : 'none', # cweb
}

self.global_commands_dict = {}
.. @+node:ekr.20110528103005.18319: *6* Script to global data structures from in modes/*.py files
import glob
import imp

theDir = g.os_path_finalize_join(g.app.loadDir,'..','modes','*.py')
aList = glob.glob(theDir)
theDir = g.os_path_finalize_join(g.app.loadDir,'..','modes')

print('-'*40,len(aList))
known_keys = list(g.app.language_delims_dict.keys())
known_keys.sort()
known = []
computed = {}
for z in aList:
    name = g.os_path_basename(z)
    name2 = name[:-3]
    if name2.startswith('__'): continue
    # if name2 in known_keys:
        # known.append(name)
        # continue
    try:
        theFile, pathname, description = imp.find_module(name2,[theDir])
        m = imp.load_module(name2, theFile, pathname, description)
    except Exception:
        g.es_exception()
        m = None
    if m:
        aList2 = [m.properties.get(z)
            for z in ('lineComment','commentStart','commentEnd')
                if m.properties.get(z)]
        print('%-20s : "%s",' % (
            '"%s"' % (name2),
            ' '.join(aList2)))
        computed[name2] = ' '.join(aList2)
mismatches = 0
for z in known_keys:
    val = g.app.language_delims_dict.get(z)
    val2 = computed.get(z)
    if not val:
        print('oops: no val',z)
    elif not val2:
        print('oops: no val2',z)
    elif val != val2:
        mismatches += 1
        print('mismatch for %s' % z)
        print(repr(val))
        print(repr(val2))
print('%s total languages' % len(aList))
print('%s new languages' % (len(list(computed.keys())) - len(known_keys)))
print('%s mismatches' % mismatches)
print('%s known language: %s' % (len(known_keys),known_keys))
.. @+node:ekr.20111013065147.9424: *5* Code cleanup
.. @+node:ekr.20111012061216.15699: *6* ?use @g.command for all Leo commands?
.. @+node:ekr.20111010093113.15548: *6* Lighten Leo's code base; remove wrapper layers
@nocolor-node

Almost from day one, Leo has defined gui base classes in the core, and
subclasses in gui plugins.

I plan to continue that organization, but I would like to remove some of the
wrapping layers if possible. The present scheme has one or two too many
redirection layers, and they are more of a nuisance than a help.

One idea would be to define **interface classes** that define the desired api's.
Unit tests could test that subclass implements the interface class, without
having to resort to quite as much error-prone machinery as at present.
.. @+node:ekr.20111101050427.16716: *5* Make g.openWithFileName "reentrant"
@nocolor-node

That is, make sure it works when called from within itself.
.. @+node:ekr.20110527225107.18351: *4* Vague
@language rest

**Important**: These items are not scheduled for any release. They will be done
only if there are specific requests for them.

Eventually, all these items will move to the dreaded to-do-later list.
.. @+node:ekr.20110529115328.18238: *5* Emacs related: 5
I'll do these if and and only if somebody asks for them.
.. @+node:ekr.20110529104352.18248: *6* Complete k.universalDispatcher
.. @+node:ekr.20110529104352.18249: *6* Complete number-to-register command
.. @+node:ekr.20031218072017.753: *6* Emacs comint-mode
@nocolor

The improved Execute Script command does most of this

Michael Manti
mmanti@mac.com

P.S. I think a feature that could make Leo *the* IDE for developing in 
interpreted languages is something like the (X)Emacs comint-mode.el for 
interacting with the shell and interpreters.

comint-mode.el serves as the basis for interactive modes for a number of
languages--OCaml, Haskell, SML, among them. It allows for editing expressions in
one buffer and triggering their evaluation in another buffer that has an
interpreter running in it, along with entering commands in the interpreter
buffer and moving back and forth through the history of their evaluation.

Imagine being able to highlight a node in Leo, and have all the code in it and
its children evaluated in an interpreter running in a separate window or pane,
much as Leo can open a Python shell now. Users of those languages could build
plug-ins specific to their language atop that layer, and the @language directive
could activate that. I think that would be very cool.
.. @+node:ekr.20071004120359.2: *6* expand-region-abbrev
See: regionalExpandAbbrev.

You may wish to expand an abbrev with a prefix attached; for example, if `cnst'
expands into `construction', you might want to use it to enter `reconstruction'.
It does not work to type recnst, because that is not necessarily a defined
abbrev. What you can do is use the command M-' (abbrev-prefix-mark) in between
the prefix `re' and the abbrev `cnst'. First, insert `re'. Then type M-'; this
inserts a hyphen in the buffer to indicate that it has done its work. Then
insert the abbrev `cnst'; the buffer now contains `re-cnst'. Now insert a
non-word character to expand the abbrev `cnst' into `construction'. This
expansion step also deletes the hyphen that indicated M-' had been used. The
result is the desired `reconstruction'.

If you actually want the text of the abbrev in the buffer, rather than its
expansion, you can accomplish this by inserting the following punctuation with
C-q. Thus, foo C-q , leaves `foo,' in the buffer.
.. @+node:ekr.20060628103226.3: *6* Make sure repeat counts work on basic editing commands
.. @+node:ekr.20111027103125.16543: *4* Code related to docs
.. @+node:ekr.20111018104244.15931: *5* Add @command print-cmd-docstrings
Leo must have a check-doc-strings script that will verify that all
commands have non-trivial doc strings.

.. @+node:ekr.20111021105253.9476: *5* Make sure all colorizer languages have entries in leoApp.py
.. @+node:ekr.20111021035504.9469: *6* Script: get all comments from modes
import glob
import imp
@others

keys = ("lineComment","commentStart","commentEnd",)
d = {}
    # Keys are language names.
    # Values are a list of comment delims, in keys order.
paths,modes_path = get_paths()
for path in paths:
    module_name = g.shortFileName(path)[:-3]
    module = import_module(module_name,modes_path)
    aList = []
    for key in keys:
        val = module.properties.get(key)
        if val: aList.append(val)
    d[module_name] = aList

print('-'* 20)
print('language_delims_dict')
for key in sorted(d):
    print('%16s: "%s"' % ('"%s"' % (key),' '.join(d.get(key))))
.. @+node:ekr.20111021035504.9470: *7* get_paths
def get_paths():
    
    modes_path = g.os_path_finalize_join(g.app.loadDir,'..','modes')
    pattern = g.os_path_finalize_join(modes_path,'*.py')
    paths = glob.glob(pattern)
    paths = [z for z in paths if not z.endswith('__init__.py')]
    return paths,modes_path
.. @+node:ekr.20111021035504.9471: *7* import_module
def import_module(module_name,modes_path):
    
    data = imp.find_module(module_name,[modes_path])
        # This can open the file.
    theFile,pathname,description = data
    module = imp.load_module(module_name,theFile,pathname,description)
    return module
.. @+node:ekr.20111019104425.15865: *5* Make sure docstrings include present documentation
.. @+node:ekr.20111018220642.15862: *3* Plugins
.. @+node:ekr.20111017132257.15882: *4* Study import_xml plugin
@language rest

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

Provides commands (Alt-x) for importing and exporting XML from a Leo
outline. These commands are to XML what ``@auto-rst`` is to
reStructuredText.

``xml2leo`` imports an .xml file into the node following the currently
selected node.  ``leo2xml`` exports the current subtree to an .xml file
the user selects.

``xml_validate``, if executed on the top node in the
Leo xml tree, reports any errors in XML generation or DTD validation,
based on the DTD referenced from the XML itself.  If there's no DTD
it reports that as an error.

``leo2xml2leo`` takes the selected Leo subtree representing an XML file,
converts it to XML internally, and then creates a new Leo subtree from
that XML after the original, with 'NEW ' at the start of the top node's
name.  This updates all the headlines, so that the convenience only
previews (see below) are updated.  The original can be deleted if the
new subtree seems correct.

Conventions
===========

This is a valid XML file::

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE dml SYSTEM "dml.dtd">
    <?xml-stylesheet href="common.css"?>
    <dml xmlns='http://example.com/' xmlns:other='http://other.com/'/>
      <block type='example'>Here's <other:b>some</other:b> text</block>
    </dml>
    <!-- This is the last line -->

Note the processing instruction (xml-stylesheet), the DTD (DOCTYPE),
the trailing comment (after the closing tag), and the pernicious
mixed content (three separate pieces of text in the ``<block/>`` element).
These commands attempt to deal with all of this.

 - A top level Leo node is created to hold these top level parts.  Its
   headline is the basename of the file.
 - The xml declaration is placed in the body of
   this top level Leo node
 - Below that, in the same body text, appears a simple namespace map::

     http://example.com/
     other: http://other.com/
     ...

   i.e. the default namespace first, and then any prefixed name spaces.
 - Below that, in the same body text, appears the ``DOCTYPE`` declaration
 - Children are added to this top level Leo node to represent the
   top level elements in the xml file.  Headlines have the following
   meanings:

       - ``? pi-target some="other" __CHK`` - i.e. questionmark,
         space, name of processing instruction target, start of processing
         instruction content.  Only the questionmark, which indicates
         the processing instruction, and the first word, which indicates
         the processing instruction target, matter.  The remainder is just
         a convenience preview of the processing instruction content, which
         is the Leo node's body text.

       - ``# This is *really* imp`` - i.e. hash,
         space, start of comment content.  Only the hash, which indicates
         the comment, matters.  The remainder is just
         a convenience preview of the comment content, which
         is the Leo node's body text.

       - ``tagname name_attribute start of element text`` - i.e. the name
         of an element followed by a convenience preview of the element's
         text content.  If the element has a ``name`` attribute that's
         included at the start of the text preview.  Only the first word
         matters, it's the name of the element.

 - Element's text is placed in the Leo node's body.  If the element has
   tailing text (the ``" text"`` tailing the ``<other:b/>`` element
   in the above example), that occurs in the Leo node's body separated
   by the `tailing text sentinel`::

       @________________________________TAIL_TEXT_______________________________

 - Element's attributes are stored in a dict ``p.v.u['_XML']['_edit']``
   on the Leo node. ``'_XML'`` is the uA prefix for these commands, and
   ``'_edit'`` is used by the ``attrib_edit`` plugin to identify
   attributes it should present to the user for editing. The
   ``attrib_edit`` plugin **should be enabled** and its ``v.u mode``
   activated (through its submenu on the Plugins menu). The attribute
   edit panel initially appears as a tab in the log pane, although it
   can be moved around by right clicking on the pane dividers if the
   ``viewrendered`` and ``free_layout`` plugins are enabled. 
.. @+node:ekr.20111004090723.15495: *4* Finish leoOPML plugin
.. @+node:ekr.20111027143736.16557: *4* Study leo_screen
@nocolor-node

Ville mentioned leoremote for driving Leo from the command line.
There's also leoscreen, for driving the command line from Leo,
providing you use screen, which is probably unix/cygwin only.
.. @+node:ekr.20111019104425.15861: *3* Scripts
.. @+node:ekr.20111018104244.15932: *4* Write create-command-docs script
@language rest

This will create large docs for individual commands from docstrings.

Base this on print-cmd-docstrings.
.. @+node:ekr.20111018104244.15933: *4* Write create-plugins-doc script (Use/adapt plugin_catalog.py)
@language rest

Leo must have a create-plugins-doc script that does the same for
plugins.  A prototype of this script exists somewhere.  Making it an
@command node will make it much more visible. 

Terry wrote plugin_catalog.py.  It is in LeoDocs.leo)
.. @+node:ekr.20110917104720.9418: *4* convert-names-to-pep8
.. @+node:ekr.20110918204546.6809: *4* convert-to-class
.. @+node:ekr.20111109083738.9798: *3* Testing
.. @+node:ekr.20111105222316.9706: *4* Unit test: no unit tests omitted
@nocolor-node

Create a "registry" of unit tests.  Verify that unitTest.leo contains them all.
.. @+node:ekr.20111105222316.9707: *4* Unit test: importing a plugin changes nothing
@nocolor-node

Menu and plugin registry methods can fail if that *particular* unit test is running.
.. @+node:ekr.20111114151846.9856: *4* Create unit test that verifies class relationships
.. @+node:ekr.20111114102224.9936: *5* << define class HighLevelInterface >>
class HighLevelInterface(object):
    
    '''A class to specify Leo's high-level editing interface
    used throughout Leo's core.
    
    The interface has two parts:
        
    1. Standard (immutable) methods that will never be overridden.
    
    2. Other (mutable) methods that subclasses may override.
    '''
    
    @others
.. @+node:ekr.20111114102224.9950: *6* ctor (HighLevelInterface)
def __init__ (self,c):
    
    self.c = c
    
    self.widget = None
    
    self.mutable_methods = (
        'flashCharacter',
        'toPythonIndex',
        'toPythonIndexRowCol',
        # 'toGuiIndex', # A synonym.
    )
.. @+node:ekr.20111114102224.9935: *6* mutable methods (HighLevelInterface)
.. @+node:ekr.20111114102224.9946: *7* flashCharacter
def flashCharacter(self,i,bg='white',fg='red',flashes=3,delay=75):
    pass
    
.. @+node:ekr.20111114102224.9943: *7* toPythonIndex (HighLevelInterface)
def toPythonIndex (self,index):
    
    s = self.getAllText()
    return g.toPythonIndex(s,index)

toGuiIndex = toPythonIndex
.. @+node:ekr.20111114102224.9945: *7* toPythonIndexRowCol (BaseTextWidget)
def toPythonIndexRowCol(self,index):
    
    # This works, but is much slower that the leoQTextEditWidget method.
    s = self.getAllText()
    i = self.toPythonIndex(index)
    row,col = g.convertPythonIndexToRowCol(s,i)
    return i,row,col
.. @+node:ekr.20111114102224.9937: *6* immutable redirection methods (HighLevelInterface)
def appendText(self,s):
    if self.widget: self.widget.appendText(s)
def delete(self,i,j=None):
    if self.widget: self.widget.delete(i,j)
def deleteTextSelection (self):
    if self.widget: self.widget.deleteTextSelection()
def get(self,i,j):
    return self.widget and self.widget.get(i,j) or ''
def getAllText(self):
    return self.widget and self.widget.getAllText() or ''
def getInsertPoint(self):
    return self.widget and self.widget.getInsertPoint() or 0
def getSelectedText(self):
    return self.widget and self.widget.getSelectedText() or ''
def getSelectionRange (self):
    return self.widget and self.widget.getSelectionRange() or (0,0)
def getYScrollPosition (self):
    return self.widget and self.widget.getYScrollPosition() or 0
def hasSelection(self):
    # Take special care with this, for the benefit of LeoQuickSearchWidget.
    # This problem only happens with the qttabs gui.
    w = self.widget
    return bool(w and hasattr(w,'hasSelection') and w.hasSelection())
def insert(self,i,s):
    if self.widget: self.widget.insert(i,s)    
def replace (self,i,j,s):
    if self.widget: self.widget.replace(i,j,s)
def see(self,i):
    if self.widget: self.widget.see(i)
def seeInsertPoint (self):
    if self.widget: self.widget.seeInsertPoint()
def selectAllText (self,insert=None):
    if self.widget: self.widget.selectAllText(insert)
def setAllText (self,s):
    if self.widget: self.widget.setAllText(s)
def setBackgroundColor(self,color):
    if self.widget: self.widget.setBackgroundColor(color)
def setFocus(self):
    if self.widget: self.widget.setFocus()
def setForegroundColor(self,color):
    if self.widget: self.widget.setForegroundColor(color)
def setInsertPoint(self,pos):
    if self.widget: self.widget.setInsertPoint(pos)
def setSelectionRange (self,i,j,insert=None):
    if self.widget: self.widget.setSelectionRange(i,j,insert=insert)
def setYScrollPosition (self,i):
    if self.widget: self.widget.setYScrollPosition(i)
def tag_configure (self,colorName,**keys):
    if self.widget: self.widget.tag_configure(colorName,**keys)
.. @+node:ekr.20111114102224.9940: *6* other immutable methods (HighLevelInterface)
# These all use leoGlobals functions or leoGui methods.

def clipboard_append(self,s):
    s1 = g.app.gui.getTextFromClipboard()
    g.app.gui.replaceClipboardWith(s1 + s)
    
def clipboard_clear (self):
    g.app.gui.replaceClipboardWith('')
    
def getFocus(self):
    return g.app.gui.get_focus(self.c)
    
def rowColToGuiIndex (self,s,row,col):
    return g.convertRowColToPythonIndex(s,row,col)   
    
# def rowColToGuiIndex (self,s,row,col):
    # return self.widget and self.widget.rowColToGuiIndex(s,row,col) or 0 

set_focus = setFocus
.. @+node:ekr.20111114102224.9950: *5* ctor (HighLevelInterface)
def __init__ (self,c):
    
    self.c = c
    
    self.widget = None
    
    self.mutable_methods = (
        'flashCharacter',
        'toPythonIndex',
        'toPythonIndexRowCol',
        # 'toGuiIndex', # A synonym.
    )
.. @+node:ekr.20111114151846.9852: *5* mustBeDefined...
.. @+node:ekr.20111114151846.9850: *6* From baseTextWidget
.. @+node:ekr.20081031074455.3: *7* baseTextWidget.mustBeDefinedOnlyInBaseClass
mustBeDefinedOnlyInBaseClass = (
    'clipboard_append', # uses g.app.gui method.
    'clipboard_clear', # usesg.app.gui method.
)
.. @+node:ekr.20081031074455.4: *7* baseTextWidget.mustBeDefinedInSubclasses
mustBeDefinedInSubclasses = (
    'appendText',
    'delete',
    'deleteTextSelection',
    'get',
    'getAllText',
    'getFocus',
    'getInsertPoint',
    'getSelectedText',
    'getSelectionRange',
    'getYScrollPosition',
    'insert',
    'see',
    'seeInsertPoint',
    'setAllText',
    'setBackgroundColor',
    'setForegroundColor',
    'setFocus',
    'setInsertPoint',
    'setSelectionRange',
    'setYScrollPosition',
)

.. @+node:ekr.20081031074455.5: *7* baseTextWidget.mustBeDefined...
# These can be do-nothings
mustBeDefined = (
    'flashCharacter',
    'hasSelection',
    'replace',
    'rowColToGuiIndex',
    'selectAllText',
    'tag_configure',
    'toGuiIndex',
    'toPythonIndex',
    'toPythonIndexRowCol',
)
.. @+node:ekr.20111114151846.9851: *6* From leoBody
.. @+node:ekr.20081005065934.9: *7* leoBody.mustBeDefined
# List of methods that must be defined either in the base class or a subclass.

mustBeDefined = (
    'after_idle',
    'forceFullRecolor', # The base-class method is usually good enough.
    'initAfterLoad',
    'tag_configure', # used in qtGui.py.
)
.. @+node:ekr.20031218072017.3660: *7* leoBody.mustBeDefinedInSubclasses
mustBeDefinedInSubclasses = (
    # Birth, death & config.
    '__init__',
    'createBindings',
    'createControl',
    'setColorFromConfig',
    'setFontFromConfig'
    # Editors
    'createEditorLabel',
    'setEditorColors',
    # Events...
    'scheduleIdleTimeRoutine',
    # Low-level gui...(May be deleted)
    'getBodyPaneHeight',
    'getBodyPaneWidth',
    'hasFocus',
    'setFocus',
)
.. @+node:ekr.20061109102912: *7* define leoBody.mustBeDefinedOnlyInBaseClass
mustBeDefinedOnlyInBaseClass = (
    'getAllText',
    'getColorizer',
    'getInsertLines',
    'getInsertPoint',
    'getSelectedText',
    'getSelectionAreas',
    'getSelectionLines',
    'getYScrollPosition',
    'hasSelection',
    'oops',
    'onBodyChanged',
    'recolor',
    'recolor_now',
    'see',
    'seeInsertPoint',
    'selectAllText',
    'setInsertPoint',
    'setSelectionRange',
    'setYScrollPosition',
    'setSelectionAreas',
    'setYScrollPosition',
    'updateSyntaxColorer',
)
.. @+node:ekr.20111114151846.9853: *6* from leoFrame
.. @+node:ekr.20080429051644.1: *7* leoFrame.mustBeDefined
# List of methods that must be defined either in the base class or a subclass.

mustBeDefined = (

    # Icon bar convenience methods.    
    'addIconButton',
    'addIconRow',
    'clearIconBar',
    'createIconBar',
    'getIconBar',
    'getIconBarObject',
    'getNewIconFrame',
    'hideIconBar',
    'initAfterLoad',
    'initCompleteHint',
    'showIconBar',
)
.. @+node:ekr.20061109120726: *7* leoFrame.mustBeDefinedOnlyInBaseClass
mustBeDefinedOnlyInBaseClass = (

    'createFirstTreeNode', # New in Leo 4.6: was defined in tkTree.
    'initialRatios',
    'longFileName',
    'oops',
    'promptForSave',
    'scanForTabWidth',
    'shortFileName',

    # Headline editing.
    'abortEditLabelCommand',
    'endEditLabelCommand',
    'insertHeadlineTime',

    # Cut/Copy/Paste.
    'OnPaste',
    'OnPasteFromMenu',
    'copyText',
    'cutText',
    'pasteText',

    # Status line convenience methods.
    'createStatusLine',
    'clearStatusLine',
    'disableStatusLine',
    'enableStatusLine',
    'getStatusLine',
    'getStatusObject',
    'putStatusLine',
    'setFocusStatusLine',
    'statusLineIsEnabled',
    'updateStatusLine',
)
.. @+node:ekr.20061109120704: *7* leoFrame.mustBeDefinedInSubclasses
mustBeDefinedInSubclasses = (
    #Gui-dependent commands.
    'cascade',
    'contractBodyPane',
    'contractLogPane',
    'contractOutlinePane',
    'contractPane',
    'equalSizedPanes',
    'expandLogPane',
    'expandPane',
    'fullyExpandBodyPane',
    'fullyExpandLogPane',
    'fullyExpandOutlinePane',
    'fullyExpandPane',
    'hideBodyPane',
    'hideLogPane',
    'hideLogWindow',
    'hideOutlinePane',
    'hidePane',
    'leoHelp',
    'minimizeAll',
    'resizeToScreen',
    'toggleActivePane',
    'toggleSplitDirection',
    # Windowutilities...
    'bringToFront',
    'deiconify',
    'get_window_info',
    'lift',
    'update',
    # Config...
    'resizePanesToRatio',
    'setInitialWindowGeometry',
    'setTopGeometry',
)
.. @+node:ekr.20111114151846.9854: *6* from leoTree
.. @+node:ekr.20081005065934.7: *7* leoTree.mustBeDefined
# List of methods that must be defined either in the base class or a subclass.

mustBeDefined = (
    'initAfterLoad', # New in Leo 4.6.
    'treeSelectHint', # New in Leo 4.6.
)
.. @+node:ekr.20061109164512: *7* leoTree.mustBeDefinedOnlyInBaseClass
mustBeDefinedOnlyInBaseClass = (
    # Getters & setters.
    'editPosition',
    'getEditTextDict',
    'setEditPosition',
    # Others.
    'endEditLabel',
    # 'expandAllAncestors', # Now defined in Commands class.
    'injectCallbacks',
    'OnIconDoubleClick',
    'onHeadChanged',
    'onHeadlineKey',
    'updateHead',
    'oops',
)
.. @+node:ekr.20061109164610: *7* leoTree.mustBeDefinedInSubclasses
mustBeDefinedInSubclasses = (
    # Colors & fonts.
    'getFont',
    'setFont',
    'setFontFromConfig ',
    # Drawing & scrolling.
    'drawIcon',
    'redraw_now',
    'scrollTo',
    # Headlines.
    'editLabel',
    # 'setEditLabelState',
    # Selecting.
    # 'select', # Defined in base class, may be overridden in do-nothing subclasses.
)
.. @+node:ekr.20111114151846.9855: *6* from leoGui
.. @+node:ekr.20061109211054: *7* leoGui.mustBeDefinedOnlyInBaseClass
mustBeDefinedOnlyInBaseClass = (
    'guiName',
    'oops',
    'setScript',
    'widget_name',
)
.. @+node:ekr.20061109211022: *7* leoGui.mustBeDefinedInSubclasses
mustBeDefinedInSubclasses = (
    # Startup & shutdown
    'attachLeoIcon',
    'center_dialog',
    'color',
    #'createComparePanel',          # optional
    #'createFindPanel',             # optional
    'createFindTab',
    # 'createKeyHandlerClass',
    'createLeoFrame',
    'createRootWindow',
    'create_labeled_frame',
    'destroySelf',
    #'eventChar',
    #'eventKeysym',
    'eventWidget',
    # 'eventXY',
    # 'finishCreate', # optional.
    # 'getFontFromParams', # optional
    # 'getFullVersion', # optional.
    'getTextFromClipboard',
    'get_focus',
    'get_window_info',
    'isTextWidget',
    # 'keysym',
    'killGui',
    # 'makeScriptButton', # optional
    'recreateRootWindow',
    'replaceClipboardWith',
    'runAboutLeoDialog',
    'runAskLeoIDDialog',
    'runAskOkCancelNumberDialog',
    'runAskOkDialog',
    'runAskYesNoCancelDialog',
    'runAskYesNoDialog',
    'runMainLoop',
    'runOpenFileDialog',
    'runSaveFileDialog',
    'set_focus',
    #'setIdleTimeHook',             # optional       
    #'setIdleTimeHookAfterDelay',   # optional
)
.. @+node:ekr.20111027103125.16540: *3* Leo's home page and web site
.. @+node:ekr.20111018104244.15919: *4* Follow up on "Leo as a static site generator
@nocolor-node

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

A long thread, full on interesting things.
.. @+node:ekr.20111018104244.15924: *4* Revise Leo's wiki
.. @+node:ekr.20111011175652.15696: *4* Register a domain name like leo-editor.org
.. @-all
.. @-leo
