#@+leo-ver=5-thin
#@+node:ekr.20051110111150: * @file leoScripts.txt
#@+all
#@+node:ekr.20111017085134.16069: ** @ignore @button nodes
#@+node:ekr.20111017085134.16072: *3* @button check-dirty
# This works, but is too slow to be really useful.

count = 0
for p in c.all_unique_positions():
    if p.isDirty():
        c.checkPythonNode (p,suppressErrors=False)
        count += 1
        
g.es('%s nodes checked' % count)
#@+node:ekr.20150508132450.1: *3* @button create decorators
g.cls()

class CreateDecorators:
    '''
    A class to create decorators from tables in getPublicCommands.
    This class uses a node called "Found: getPublicCommands".
    
    This @button node must be executed in LeoPy.leo.
    '''
    def __init__(self,c):
        self.c = c
        self.fixups = self.create_fixups()
        self.n = 0
        self.n_fail = 0
    @others

CreateDecorators(c).run()
#@+node:ekr.20150508132450.2: *4* create_d
def create_d(self,lines,publicCommands):
    '''Create a dict. keys are method names; values are command names.'''
    trace = False
    if trace:
        print('')
        g.trace(publicCommands.h)
    d = {}
    for s in lines:
        aList = s.split()
        if len(aList) > 2:
            aList = [aList[0],' '.join(aList[1:])]
        c_name,f_name = aList[0].strip(),aList[1].strip()
        if ' ' not in f_name:
            f_name = f_name.split('.')[-1]
        # if '(' in f_name:
            # f_name = f_name[:f_name.find('(')]
        if trace: g.trace('%45s %s' % (c_name,f_name))
        d [f_name] = c_name
    return d
#@+node:ekr.20150508132450.3: *4* create_decorator
def create_decorator(self,c_name,f_name,root):
    '''
    Search root for a definition of f_name.
    If found, insert @cmd(f_name) before the definition.
    '''
    # g.trace('%45s %s' % (c_name,f_name))
    found = False
    for p in root.self_and_subtree():
        result = []
        for s in g.splitLines(p.b):
            if g.match_word(s,0,'def ' + f_name):
                if found:
                    g.trace('duplicate def',f_name)
                else:
                    found = True
                    decorator = '@cmd(%s)\n' % (c_name)
                    result.append(decorator)
                    # print('%s%s' % (decorator,s))
            result.append(s)
    return found
#@+node:ekr.20150508132450.4: *4* create_decorators
def create_decorators(self,d,root):
    '''Create decorators for all items in d in root's tree.'''
    print('\n%s\n' % root.h)
    if root.h in self.fixups:
        roots = []
        aList = self.fixups.get(root.h)
        for root2_h in aList:
            root2 = g.findNodeAnywhere(self.c,root2_h)
            if root2:
                g.trace(root.h,'=====>',root2.h)
                roots.append(root2)
            else:
                g.trace('===== not found',root2_h)
    else:
        roots = [root]
    for f_name in sorted(d.keys()):
        found = False
        for root in roots:
            c_name = d.get(f_name)
            found = self.create_decorator(c_name,f_name,root)
            if found: break
        if not found:
            g.trace('not found',f_name)
            self.n_fail += 1
#@+node:ekr.20150508132450.5: *4* create_fixups
def create_fixups(self):
    '''
    Return a fixup dict.
    Keys are headlines for classes.
    Values are new headlines of nodes containing the actual class.
    '''
    return {
        'ChapterCommandsClass': ['class ChapterController'],
        'EditCommandsClass': [
            'EditCommandsClass',
            'class Commands',
            'class LeoQtFrame',
            'class LeoBody',
        ],
        'class SearchCommandsClass': ['class LeoFind (LeoFind.py)'],
        'KeyHandlerCommandsClass (add docstrings)': [
            'class KeyHandlerClass',
            'class AutoCompleterClass',
        ]
    }
#@+node:ekr.20150508132450.6: *4* find_class
def find_class(self,p):
    '''Return the position of the class enclosing p.'''
    for p2 in p.parents():
        if p2.h.lower().find('class') > -1 and p2.b.find('class') > -1:
            return p2
    else:
        g.trace('*** no class for p.h')
        return None
#@+node:ekr.20150508132450.7: *4* find_next_clone
def find_next_clone(self,p):
    v = p.v
    p = p.copy()
    p.moveToThreadNext()
    wrapped = False
    while 1:
        # g.trace(p.v,p.h)
        if p and p.v == v:
            break
        elif p:
            p.moveToThreadNext()
        elif wrapped:
            break
        else:
            wrapped = True
            p = c.rootPosition()
    return p
#@+node:ekr.20150508132450.8: *4* munge_lines
def munge_lines(self,root,publicCommands):
    '''Return munged lines of '''
    # print('')
    # g.trace(root.h)
    s = publicCommands.b
    i,j = s.find('{'),s.find('}')
    s = s[i+1:j]
    # print(s)
    lines = sorted([z.strip() for z in g.splitLines(s) if z.strip()])
    lines = [z for z in lines if not z.startswith('#')]
    lines = [z[:z.find('#')] if z.find('#') > -1 else z for z in lines]
    lines = [z.rstrip().rstrip(',') for z in lines]
    lines = [z[1:] for z in lines]
    lines = [z.replace("':",' ') for z in lines]
    # print('\n'.join(lines))
    self.n += len(lines)
    return lines
#@+node:ekr.20150508132450.9: *4* run
def run(self):
    '''Top-level code.'''
    self.n = 0
    found = g.findNodeAnywhere(c,'Found: getPublicCommands')
    assert found
    for child in found.children():
        publicCommands = self.find_next_clone(child)
        root = self.find_class(publicCommands)
        if root:
            lines = self.munge_lines(root,publicCommands)
            d = self.create_d(lines,publicCommands)
            self.create_decorators(d,root)
    print('\n%s commands %s failed' % (self.n,self.n_fail))
#@+node:ekr.20111017085134.16070: *3* @button jython
# **** Change these as needed ***
import os, sys

tempFileName = g.os_path_abspath(
    g.os_path_join(g.app.testDir,'jythonScript.py'))

if sys.platform == 'win32':
    jython = r'C:\Jython-21\jython.bat'
    command = '%s "%s"' % (jython,tempFileName) 
else:
    jython = r'cd ~/jython-2.1 ; ./jython'
    command = '%s "%s" &' % (jython,tempFileName)

script = g.getScript(c,p).strip()
if not script:
    s = "no script in %s" % p.h
    g.es(s) ; print s
else:
    f = file(tempFileName,'w')
    f.write(script + '\n')
    f.close()
    print command
    os.system(command)
#@+node:ekr.20111017085134.16074: *3* @button Lines Down
# Move selected text of the body down one line.

@others

moveLinesDown(c,p)
#@+node:ekr.20111017085134.16075: *4* moveLinesDown
def moveLinesDown(c,p):
    
    undoType = 'Move Lines Down'
    head,lines,tail,oldSel,oldYview = c.getBodyLines()
    if not tail or not lines: return
    tail = g.splitLines(tail)[1:]
    lines = '\n'.join(lines) + '\n'
    lines = g.splitLines(lines)
    # Move the lines down.
    lines.insert(0,tail[0])
    tail = tail[1:]
    if not tail: lines.insert(1,'\n')
    # Convert back to strings.
    tail = ''.join(tail)
    lines = ''.join(lines)
    # Adjust selection.
    sel1,sel2 = oldSel
    row,col = sel1.split('.') ; sel1 = '%d.%s' % (int(row)+1,col)
    row,col = sel2.split('.') ; sel2 = '%d.%s' % (int(row)+1,col)
    newSel = sel1,sel2
    c.updateBodyPane(head,lines,tail,undoType,newSel,oldYview)
#@+node:ekr.20111017085134.16076: *3* @button Lines Up
# Move selected text of the body up one line.

@others

moveLinesUp(c,p)
#@+node:ekr.20111017085134.16077: *4* moveLinesUp
def moveLinesUp(c,p):
    
    undoType = 'Move Lines Up'
    head,lines,tail,oldSel,oldYview = c.getBodyLines()
    if not head or not lines: return
    # A slight adjustment: lines doesn't end in '\n'.
    head = g.splitLines(head)
    lines = '\n'.join(lines) + '\n'
    lines = g.splitLines(lines)
    # Move the lines up.
    lines.append(head[-1])
    head = head[:-1]
    # Convert back to strings.
    head = ''.join(head)
    lines = ''.join(lines)[:-1] # Undo the adjustment above.
    # Adjust selection.
    sel1,sel2 = oldSel
    row,col = sel1.split('.') ; sel1 = '%d.%s' % (int(row)-1,col)
    row,col = sel2.split('.') ; sel2 = '%d.%s' % (int(row)-1,col)
    newSel = sel1,sel2
    c.updateBodyPane(head,lines,tail,undoType,newSel,oldYview)
#@+node:ekr.20111017085134.16078: *3* @button lispCall @key=alt+6
'''Analyze external calls in lisp files.'''

import glob
class lispAnalyzer:
    @others

base = '/media/disk/XEmacs/XEmacs-21.4.13/lisp/*.el'
paths = glob.glob(base)
# g.es(g.listToString(paths))
paths = [paths[0]]

x = lispAnalyzer(c,paths)
x.run()
#@+node:ekr.20111017085134.16079: *4* ctor
def __init__ (self,c,paths):

    self.c = c
    self.paths = paths

    # Debugging.
    self.debug = True
    self.trace = False

    # Semantic info.
    self.def_keywords = ('defun','defvar',)
    self.indent_keywords = ('if','prog','prog1','progn','set',)
    self.expr_keywords = ('and','not','or',)

    self.indent = 0 # Indentation of production output.

    # Dispatch dictionary.
    self.dispatchDict = {}
#@+node:ekr.20111017085134.16080: *4* run & helpers
def run(self):

    fn = self.paths[0]
    f = open(fn)
    s = f.read()
    g.es_print(fn)
    # g.trace(s)
    s = self.stripStrings(s)
    s = self.stripComments(s)
    defs = self.findDefs(s)
    calls = self.findCalls(s,defs)
    calls = self.stripLispIds(calls)
    defs.sort()
    calls.sort()
    g.trace('defs...\n',g.listToString(defs))
    g.trace('calls...\n',g.listToString(calls))
#@+node:ekr.20111017085134.16081: *5* findCalls
def findCalls (self,s,defs):

    calls = []
    i = 0
    while i < len(s):
        progress = i
        ch = s[i]
        if ch.isalnum() or ch == u'_':
            j = self.skipId(s,i)
            theId = s[i:j].strip()
            if theId not in defs and theId not in calls:
                calls.append(theId)
            i = j
        else: i += 1
        assert progress < i

    return calls       
#@+node:ekr.20111017085134.16082: *5* findDefs
def findDefs (self,s):

    defs = [] ; tag = '(defun'
    i = 0
    while True:
        progress = i
        j = s.find(tag,i)
        if j == -1:
            return defs
        i = j + len(tag)
        j = g.skip_ws(s,i)
        if j > i:
            k = self.skipId(s,j)
            if k > j:
                defs.append(s[j:k])
            i = k
        assert progress < i 
#@+node:ekr.20111017085134.16083: *5* stripComments
def stripComments(self,s):

    result = []
    i = 0
    while i < len(s):
        progress = i
        ch = s[i]
        if ch == ';':
            i = self.skipComment(s,i)
        else:
            result.append(ch)
            i += 1
        assert progress < i

    return ''.join(result)
#@+node:ekr.20111017085134.16084: *5* stripLispIds
def stripLispIds (self,aList):

    lispIds = [
        't','f',
        'apply',
        'boundp',
        'car','cons','cdr','consp',
        'eq','equal','eval',
        'lamda','lambda',
        'macro',
        'nil','not','null','numberp',
        'or',
        'unless',
        'vector','vectorp',
        # special forms
        'and',
        'catch',
        'cond',
        'condition-case',
        'defconst',
        'defmacro',
        'defun',
        'defvar',
        'function',
        'if',
        'interactive',
        'let',
        'let*',
        'or',
        'prog1',
        'prog2',
        'progn',
        'quote',
        'save-current-buffer',
        'save-excursion',
        'save-restriction',
        'save-window-excursion',
        'setq',
        'setq-default',
        'track-mouse',
        'unwind-protect',
        'while',
        'with-output-to-temp-buffer',
    ]

    return [z for z in aList if z not in lispIds]
#@+node:ekr.20111017085134.16085: *5* stripStrings
def stripStrings(self,s):

    result = []
    i = 0
    while i < len(s):
        progress = i
        ch = s[i]
        if ch == '"':
            i = self.skipString(s,i)
        elif ch == "'":
            i = self.skipId(s,i+1)
        else:
            result.append(ch)
            i += 1
        assert progress < i

    return ''.join(result)
#@+node:ekr.20111017085134.16086: *4* class token
class token:

    '''Representing one elisp syntactic entity,
    with a list of preceding comments.'''

    def __init__ (self,comments,kind,val):
        self.comments = comments[:]
        self.kind = kind
        self.val = val
            # For blocks, a list of tokens.
            # For all other tokens, the spelling of the token.

    def __repr__ (self):
        return '<token kind: %s, val: %s>' % (self.kind,self.val)

    def __str__ (self):
        if self.kind == 'block:':
            return 'block: [snip]'
        elif self.kind == 'string:':
            return '%s%s' % (self.kind,self.val[:20])
        else:
            return '%s%s' % (self.kind,self.val)
#@+node:ekr.20111017085134.16087: *4* choose
def choose(self,cond,a,b): # warning: evaluates all arguments

    if cond: return a
    else: return b
#@+node:ekr.20111017085134.16088: *4* dumpList
def dumpList(self,aList):

    if type(aList) == type([]):
        result = self.dumpListHelper(aList,indent=0)
        return '\n'.join(result)
    else:
        return repr(aList)

def dumpListHelper(self,aList,indent):

    result = []
    leading = ' ' * (4 * indent)

    for z in aList:
        if z is None:
            result.append('%s%s' % (leading,'None'))
        elif z == []:
            result.append('%s%s' % (leading,'[]'))
        elif type(z) == type([]):
            result.append('%s%s' % (leading,'['))
            result.extend(self.dumpListHelper(z,indent+1))
            result.append('%s%s' % (leading,']'))
        elif isinstance(z,self.token):
            if z.kind=='block:':
                result.append('%s%s' % (leading,'block:'))
                result.extend(self.dumpListHelper(z.val,indent+1))
                # result.append('%s%s' % (leading,'block:]'))
            else:
                result.append('%s%s' % (leading,str(z)))
        else:
            result.append('%s%s' % (leading,str(z)))

    return result

listToString = dumpList
#@+node:ekr.20111017085134.16089: *4* Parsing...
#@+node:ekr.20111017085134.16090: *5* parse
def parse(self,s):

    # Generate the nodes, including directive and section references.
    return self.scanForest(s)
#@+node:ekr.20111017085134.16091: *5* scan & helpers
def scan(self,s,i):

    '''Scan an elisp expression.'''

    start = i ; end = len(s) ; result = []
    comments = [] ; token = self.token
    # A hack. ignore initial @language lisp
    tag = '@language lisp'
    if i == 0 and s[i:i+len(tag)]==tag:
        i += len(tag)
    while i < end:
        progress = i
        ch = s[i]
        if ch == ';':
            j = self.skipComment(s,i)
            start = i = j
        elif ch == '"':
            j = self.skipString(s,i)
            start = i = j
        elif ch.isalnum() or ch == u'_':
            j = self.skipId(s,i)
            result.append(token(comments,'id:',s[i:j]))
            start = i = j
        elif ch =='(':
            i += 1
            j,aList = self.scan(s,i)
            result.append(token(comments,'block:',aList))
            start = i = j
        elif ch == ')':
            i += 1
            return i,result
        else:
            # if ch == "'": ch = 'quote'
            # if ch not in (' ','\t','\n','\r'):
                # result.append(token(comments,'op:',ch))
            i += 1
        assert progress < i,'i: %d, ch: %s' % (i,repr(s[i]))

    if start < end:
        tail = s[start:end].strip()
        if tail:
            result.append(token(comments,'tail:',tail))
            comment = []
    if comments:
        result.append(token(comments,'trailing-comment:',''))

    return i,result
#@+node:ekr.20111017085134.16092: *6* skipComment
def skipComment (self,s,i):

    '''Skip a comment.'''

    while i < len(s):
        if s[i] == '\n':
            break
        i += 1
    return i
#@+node:ekr.20111017085134.16093: *6* skipId
def skipId (self,s,i):

    while i < len(s):
        ch = s[i]
        if ch.isalnum() or ch in ('_','-'):
            i += 1
        else:
            break
    return i
#@+node:ekr.20111017085134.16094: *6* skipString
def skipString(self,s,i):

    """Skip a string literal."""

    assert(s[i] == '"')
    i += 1
    while i < len(s):
        ch = s[i]
        if ch == '\\' : i += 2
        elif ch == '"':
            i += 1 ; break
        else: i += 1

    return i
#@+node:ekr.20111017085134.16095: *5* scanForest
def scanForest (self,s):

    i = 0 ; result = []

    while i < len(s):
        progress = i
        i,aList = self.scan(s,i)
        aList and result.extend(aList)
        assert i > progress

    return result


#@+node:ekr.20111017085134.16096: *4* Code generators...
@ From Richard Deibenkorn:

1. Attempt what is not certain. Certainty may or may not come later. It may then
be a valuable delusion.

2. The pretty, initial position which falls short of completeness is not to be
valued--except as stimulus for further moves.

3. Do search.  But in order to find other than what is looked for.

4. Use and respond to the initial fresh qualities but consider them absolutely
expendable.
#@+node:ekr.20111017085134.16097: *5* gen
def gen(self,tokens,indent,init=False):

    result = []

    if init: result.append('='*40)

    for token in tokens:
        aList = self.gen_token(token,indent)
        result.extend(aList)

    if init: result.append('-'*40)

    return result

#@+node:ekr.20111017085134.16098: *5* gen_token
def gen_token(self,token,indent):

    result = []

    if self.debug:
        if token.kind == 'block:':
            aList = self.gen_block(token,indent)
            result.extend(aList)
        else:
            self.put_token(token,indent,result)
    else:
        if token.kind == 'block:':
            self.gen_block(token,indent)
        else:
            self.put_code_token(token)

    return result
#@+node:ekr.20111017085134.16099: *5* gen_block & helper
def gen_block (self,token,indent):

    if not (token and token.val):
        return []

    blockList = token.val
    token2 = blockList[0]
    result = []

    if token2.kind.startswith('id'):
        aList = self.gen_block_id(token2.val,blockList,indent)
    else:
        if self.debug:
            self.put('block...',[],indent,result)
        aList = self.gen(token.val,indent+1)

    result.extend(aList)
    return result
#@+node:ekr.20111017085134.16100: *6* gen_block_id
def gen_block_id (self,theId,tokens,indent):

    result = []

    # Eventually there will be a lookup of the dispatch dict here.
    if theId == 'let':
        aList = self.gen_let(tokens,indent)
    elif theId == 'if':
        aList = self.gen_if(tokens,indent)
    elif theId in self.def_keywords:
        aList = self.gen_def(theId,tokens,indent)
    elif theId in self.indent_keywords:
        self.put('%s...' % (theId),[],indent,result)
        aList = self.gen(tokens[1:],indent+1)
    elif theId in self.expr_keywords:
        aList = self.gen_expr(theId,tokens[1:],indent+1)
    else:
        aList = self.gen_call(theId,tokens[1:],indent)

    result.extend(aList)
    return result
#@+node:ekr.20111017085134.16101: *5* gen_call & helper
def gen_call (self,funcId,tokens,indent):

    result = []

    if self.debug:
        self.put('call: %s' % (funcId),[],indent,result)
        for token in tokens:
            aList = self.gen_arg(token,indent+1)
            result.extend(aList)
    else:
        self.put_code_line('%s(' % (funcId))
        for token in tokens:
            self.gen_arg(token,indent+1)
        self.put_code(')')

    return result

#@+node:ekr.20111017085134.16102: *6* gen_arg
def gen_arg(self,token,indent):

    result = []

    if self.debug:
        if token.kind == 'block:':
            self.put('arg block:...',[],indent,result)
            aList = self.gen_block(token,indent)
            result.extend(aList)
        else:
            self.put_token(token,indent,result)
    else:
        if token.kind == 'block:':
            aList = self.gen_block(token,indent)
            self.put_code(''.join(aList))
        else:
            self.put_code_token(token)

    return result
#@+node:ekr.20111017085134.16103: *5* gen_def
def gen_def(self,theId,tokens,indent):

    result = []

    if not tokens or len(tokens) < 3:
        result.append('*** bad def tokens')
        return result

    defToken,idToken = tokens[0:2]
    if idToken.kind != 'id:':
        result.append('*** bad def id')
        return result

    if self.debug:
        self.put(theId,idToken.val,indent,result)
        aList = self.gen(tokens[2:],indent+1)
        result.extend(aList)
    else:
        self.put_code('def %s (' % idToken.val)
        self.gen_token(tokens[2],indent)
        self.put_code('): # end def\n')
        self.indent += 1
        self.gen(tokens[3:],indent+1)
        self.indent -= 1

    return result
#@+node:ekr.20111017085134.16104: *5* gen_if & helpers
@ if condition then-form else-forms.

If the evaluated condition is non-nil, then-form is evaluated and the result
returned. Otherwise, the else-forms are evaluated in textual order, and the
value of the last one is returned. If condition has the value nil, and no
else-forms are given, if returns nil.
@c

def gen_if (self,tokens,indent):

    # tokens[0]: id:if
    # tokens[1] cond

    for i in xrange(len(tokens)):
        g.trace('tokens[%d]: %s' % (i,self.dumpList(tokens[i])))

    # g.trace(self.dumpList(tokens))

    result = []

    if self.debug:
        self.put('if...',[],indent,result)
        aList = self.gen(tokens[1:],indent+1)
        result.extend(aList)
    else:
        self.put_code('if ')
        self.gen(tokens[1:],indent+1)
        self.put_code(': # end if\n')

    return result
#@+node:ekr.20111017085134.16105: *6* gen_then
def gen_then (self,token):

    pass
#@+node:ekr.20111017085134.16106: *5* gen_expr
def gen_expr (self,theId,aList,indent):

    binops = ('and','or',)
    result = []

    self.put(theId,[],indent,result)
    aList = self.gen(aList,indent+1)

    result.extend(aList)
    return result
#@+node:ekr.20111017085134.16107: *5* gen_let & helper
@
(let ((variable value)
      (variable value)
      ...)
  body...)
@c

def gen_let (self,tokens,indent):

    if not tokens: return []
    if len(tokens) != 3:
        g.trace('unusual let')
        result = self.gen(tokens,indent+1)
        return result

    if 0:
        for i in xrange(len(tokens)):
            g.trace('token',i,tokens[i])

    letToken,bindingToken,bodyToken = tokens
    result = []
    self.put('let...',[],indent,result)
    self.put('let-bindings...',[],indent+1,result)
    aList = self.gen_let_bindings(bindingToken,indent+2)
    result.extend(aList)
    self.put('let-block...',[],indent+1,result)
    aList = self.gen_block(bodyToken,indent+2)
    result.extend(aList)
    return result
#@+node:ekr.20111017085134.16108: *6* gen_let_bindings
def gen_let_bindings (self,token,indent):

    result = []

    if token.kind != 'block:':
        g.trace('unexpected let')
        return result

    for z in token.val:
        if z.kind == 'block:': # one (id,val) pair
            if z.val and len(z.val) == 2:
                token1 = z.val[0]
                token2 = z.val[1]
                self.put('let-id',token1,indent,result)
                self.put('let-val...',[],indent,result)
                if token2.kind == 'block:':
                    aList = self.gen_block(token2,indent+1)
                    result.extend(aList)
                else:
                    #g.trace('no let list')
                    self.put_token(token2,indent+1,result)
            else:
                g.trace('unexpected let 2')

    return result
#@+node:ekr.20111017085134.16109: *5* put...
#@+node:ekr.20111017085134.16110: *6* put
def put (self,kind,val,indent,result):

    '''Append one or more lines of output to result.'''

    leading = '%2d: %s' % (indent,' ' * indent)

    if kind == 'string:':
        val = self.choose(len(val)>20,val[:20]+'..."',val)

    if val:
        s = '%s%s %s' % (leading,str(kind),str(val))
    else:
        s = '%s%s' % (leading,str(kind))

    result.append(s)
#@+node:ekr.20111017085134.16111: *6* put_token
def put_token (self,token,indent,result):

    for z in token.comments:
        self.put('comment:',z,indent,result)

    self.put(token.kind,token.val,indent,result)
#@+node:ekr.20111017085134.16112: *6* put_code & put_code_line
def put_code_line (self,s):

    s2 = '%s%s' % (' '*self.indent,s)
    print s2,

def put_code (self,s):

    print s,
#@+node:ekr.20111017085134.16113: *6* put_code_token
def put_code_token (self,token):

    if token.kind == 'block:':
        self.put_code('<block>')
    else:
        self.put_code(token.val)

#@+node:ekr.20160123185308.1: *3* @button make_stub_files script
'''
Make a stub file in the ~/home directory for every @<file> node in the
selected tree.
'''
import ast
import os
import textwrap
import leo.core.leoGlobals as g
@others
MakeStubFile(c).run(c.p)
#@+node:ekr.20160123185308.2: *4* class MakeStubFile
class MakeStubFile:
    '''A class to make Python stub (.pyi) files.'''
    @others
#@+node:ekr.20160123185308.3: *5* msf.ctors & helpers
def __init__(self, c):
    self.c = c
    self.d = self.scan_types_data(c) or self.make_types_dict(c)
        # Keys are strings, values are Type objects.
#@+node:ekr.20160123185308.4: *6* msf.make_types_dict
def make_types_dict(self, c):
    '''Return a dict whose keys are names and values are type specs.'''
    return {
        'aList': 'Sequence',
        'aList2': 'Sequence',
        'c': 'Commander',
        'i': 'int',
        'j': 'int',
        'k': 'int',
        'node': 'ast.Ast',
        'p': 'Position',
        's': 'str',
        's2': 'str',
        'v': 'VNode',
    }
#@+node:ekr.20160123185308.5: *6* msf.scan_types_data
def scan_types_data(self, c):
    '''Create self.d from @data stub-types nodes.'''
    aList = c.config.getData(
        'stub-types',
        strip_comments=True,
        strip_data=True)
    d = {}
    for s in aList:
        name, value = s.split(None,1)
        d[name.strip()] = value.strip()
    if False:
        for key in sorted(d.keys()):
            g.trace(key, d.get(key))
    return d
#@+node:ekr.20160123185308.6: *5* msf.make_stub_file
def make_stub_file(self, p):
    '''Make a stub file in ~/stubs for the @<file> node at p.'''
    import ast
    import leo.core.leoAst as leoAst
    assert p.isAnyAtFileNode()
    c = self.c
    fn = p.anyAtFileNodeName()
    if not fn.endswith('.py'):
        g.es_print('not a python file', fn)
        return
    ### abs_fn = g.os_path_finalize_join(g.app.loadDir, fn)
    abs_fn = g.fullPath(c, p)
    if not g.os_path_exists(abs_fn):
        g.es_print('not found', abs_fn)
        return
    stubs = g.os_path_finalize(g.os_path_expanduser('~/stubs'))
    if g.os_path_exists(stubs):
        base_fn = g.os_path_basename(fn)
        out_fn = g.os_path_finalize_join(stubs,base_fn)
    else:
        g.es_print('not found', stubs)
        return
        # out_fn = g.os_path_finalize_join(g.app.loadDir, fn)
    out_fn = out_fn[:-3] + '.pyi'
    s = open(abs_fn).read()
    node = ast.parse(s,filename=fn,mode='exec')
    leoAst.StubTraverser(self.c, self.d, out_fn).run(node)
#@+node:ekr.20160123185308.7: *5* msf.run
def run(self, p):
    '''Make stub files for all files in p's tree.'''
    if p.isAnyAtFileNode():
        self.make_stub_file(p)
        return
    # First, look down tree.
    after, p2 = p.nodeAfterTree(), p.firstChild()
    found = False
    while p2 and p != after:
        if p2.isAnyAtFileNode():
            self.make_stub_file(p2)
            p2.moveToNext()
            found = True
        else:
            p2.moveToThreadNext()
    if not found:
        # Look up the tree.
        for p2 in p.parents():
            if p2.isAnyAtFileNode():
                self.make_stub_file(p2)
                break
        else:
            g.es('no files found in tree:', p.h)
#@+node:ekr.20160123185308.8: *4* class AstFormatter
class AstFormatter:
    '''
    A class to recreate source code from an AST.
    
    This does not have to be perfect, but it should be close.
    
    Also supports optional annotations such as line numbers, file names, etc.
    '''
    # No ctor.
    # pylint: disable=consider-using-enumerate
    @others
#@+node:ekr.20160123185308.9: *5*  f.Entries
#@+node:ekr.20160123185308.10: *6* f.format
def format(self, node):
    '''Format the node (or list of nodes) and its descendants.'''
    self.level = 0
    val = self.visit(node)
    return val and val.strip() or ''
#@+node:ekr.20160123185308.11: *6* f.visit
def visit(self, node):
    '''Return the formatted version of an Ast node, or list of Ast nodes.'''
    if isinstance(node, (list, tuple)):
        return ','.join([self.visit(z) for z in node])
    elif node is None:
        return 'None'
    else:
        assert isinstance(node, ast.AST), node.__class__.__name__
        method_name = 'do_' + node.__class__.__name__
        method = getattr(self, method_name)
        s = method(node)
        # pylint: disable=unidiomatic-typecheck
        assert type(s) == type('abc'), type(s)
        return s
#@+node:ekr.20160123185308.12: *5* f.Contexts
#@+node:ekr.20160123185308.13: *6* f.ClassDef
# ClassDef(identifier name, expr* bases, stmt* body, expr* decorator_list)

def do_ClassDef(self, node):
    result = []
    name = node.name # Only a plain string is valid.
    bases = [self.visit(z) for z in node.bases] if node.bases else []
    if bases:
        result.append(self.indent('class %s(%s):\n' % (name, ','.join(bases))))
    else:
        result.append(self.indent('class %s:\n' % name))
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.14: *6* f.FunctionDef
# FunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list)

def do_FunctionDef(self, node):
    '''Format a FunctionDef node.'''
    result = []
    if node.decorator_list:
        for z in node.decorator_list:
            result.append('@%s\n' % self.visit(z))
    name = node.name # Only a plain string is valid.
    args = self.visit(node.args) if node.args else ''
    result.append(self.indent('def %s(%s):\n' % (name, args)))
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.15: *6* f.Interactive
def do_Interactive(self, node):
    for z in node.body:
        self.visit(z)
#@+node:ekr.20160123185308.16: *6* f.Module
def do_Module(self, node):
    assert 'body' in node._fields
    result = ''.join([self.visit(z) for z in node.body])
    return result # 'module:\n%s' % (result)
#@+node:ekr.20160123185308.17: *6* f.Lambda
def do_Lambda(self, node):
    return self.indent('lambda %s: %s' % (
        self.visit(node.args),
        self.visit(node.body)))
#@+node:ekr.20160123185308.18: *5* f.Expressions
#@+node:ekr.20160123185308.19: *6* f.Expr
def do_Expr(self, node):
    '''An outer expression: must be indented.'''
    return self.indent('%s\n' % self.visit(node.value))
#@+node:ekr.20160123185308.20: *6* f.Expression
def do_Expression(self, node):
    '''An inner expression: do not indent.'''
    return '%s\n' % self.visit(node.body)
#@+node:ekr.20160123185308.21: *6* f.GeneratorExp
def do_GeneratorExp(self, node):
    elt = self.visit(node.elt) or ''
    gens = [self.visit(z) for z in node.generators]
    gens = [z if z else '<**None**>' for z in gens] ### Kludge: probable bug.
    return '<gen %s for %s>' % (elt, ','.join(gens))
#@+node:ekr.20160123185308.22: *6* f.ctx nodes
def do_AugLoad(self, node):
    return 'AugLoad'

def do_Del(self, node):
    return 'Del'

def do_Load(self, node):
    return 'Load'

def do_Param(self, node):
    return 'Param'

def do_Store(self, node):
    return 'Store'
#@+node:ekr.20160123185308.23: *5* f.Operands
#@+node:ekr.20160123185308.24: *6* f.arguments
# arguments = (expr* args, identifier? vararg, identifier? kwarg, expr* defaults)

def do_arguments(self, node):
    '''Format the arguments node.'''
    kind = self.kind(node)
    assert kind == 'arguments', kind
    args = [self.visit(z) for z in node.args]
    defaults = [self.visit(z) for z in node.defaults]
    # Assign default values to the last args.
    args2 = []
    n_plain = len(args) - len(defaults)
    for i in range(len(args)):
        if i < n_plain:
            args2.append(args[i])
        else:
            args2.append('%s=%s' % (args[i], defaults[i - n_plain]))
    # Now add the vararg and kwarg args.
    name = getattr(node, 'vararg', None)
    if name: args2.append('*' + name)
    name = getattr(node, 'kwarg', None)
    if name: args2.append('**' + name)
    return ','.join(args2)
#@+node:ekr.20160123185308.25: *6* f.arg (Python3 only)
# Python 3:
# arg = (identifier arg, expr? annotation)

def do_arg(self, node):
    if node.annotation:
        return self.visit(node.annotation)
    else:
        return ''
#@+node:ekr.20160123185308.26: *6* f.Attribute
# Attribute(expr value, identifier attr, expr_context ctx)

def do_Attribute(self, node):
    return '%s.%s' % (
        self.visit(node.value),
        node.attr) # Don't visit node.attr: it is always a string.
#@+node:ekr.20160123185308.27: *6* f.Bytes
def do_Bytes(self, node): # Python 3.x only.
    assert g.isPython3
    return str(node.s)
#@+node:ekr.20160123185308.28: *6* f.Call & f.keyword
# Call(expr func, expr* args, keyword* keywords, expr? starargs, expr? kwargs)

def do_Call(self, node):
    # g.trace(node,Utils().dump_ast(node))
    func = self.visit(node.func)
    args = [self.visit(z) for z in node.args]
    for z in node.keywords:
        # Calls f.do_keyword.
        args.append(self.visit(z))
    if getattr(node, 'starargs', None):
        args.append('*%s' % (self.visit(node.starargs)))
    if getattr(node, 'kwargs', None):
        args.append('**%s' % (self.visit(node.kwargs)))
    args = [z for z in args if z] # Kludge: Defensive coding.
    return '%s(%s)' % (func, ','.join(args))
#@+node:ekr.20160123185308.29: *7* f.keyword
# keyword = (identifier arg, expr value)

def do_keyword(self, node):
    # node.arg is a string.
    value = self.visit(node.value)
    # This is a keyword *arg*, not a Python keyword!
    return '%s=%s' % (node.arg, value)
#@+node:ekr.20160123185308.30: *6* f.comprehension
def do_comprehension(self, node):
    result = []
    name = self.visit(node.target) # A name.
    it = self.visit(node.iter) # An attribute.
    result.append('%s in %s' % (name, it))
    ifs = [self.visit(z) for z in node.ifs]
    if ifs:
        result.append(' if %s' % (''.join(ifs)))
    return ''.join(result)
#@+node:ekr.20160123185308.31: *6* f.Dict
def do_Dict(self, node):
    result = []
    keys = [self.visit(z) for z in node.keys]
    values = [self.visit(z) for z in node.values]
    if len(keys) == len(values):
        result.append('{\n' if keys else '{')
        items = []
        for i in range(len(keys)):
            items.append('  %s:%s' % (keys[i], values[i]))
        result.append(',\n'.join(items))
        result.append('\n}' if keys else '}')
    else:
        print('Error: f.Dict: len(keys) != len(values)\nkeys: %s\nvals: %s' % (
            repr(keys), repr(values)))
    return ''.join(result)
#@+node:ekr.20160123185308.32: *6* f.Ellipsis
def do_Ellipsis(self, node):
    return '...'
#@+node:ekr.20160123185308.33: *6* f.ExtSlice
def do_ExtSlice(self, node):
    return ':'.join([self.visit(z) for z in node.dims])
#@+node:ekr.20160123185308.34: *6* f.Index
def do_Index(self, node):
    return self.visit(node.value)
#@+node:ekr.20160123185308.35: *6* f.List
def do_List(self, node):
    # Not used: list context.
    # self.visit(node.ctx)
    elts = [self.visit(z) for z in node.elts]
    elst = [z for z in elts if z] # Defensive.
    return '[%s]' % ','.join(elts)
#@+node:ekr.20160123185308.36: *6* f.ListComp
def do_ListComp(self, node):
    elt = self.visit(node.elt)
    gens = [self.visit(z) for z in node.generators]
    gens = [z if z else '<**None**>' for z in gens] ### Kludge: probable bug.
    return '%s for %s' % (elt, ''.join(gens))
#@+node:ekr.20160123185308.37: *6* f.Name
def do_Name(self, node):
    return node.id
#@+node:ekr.20160123185308.38: *6* f.Num
def do_Num(self, node):
    return repr(node.n)
#@+node:ekr.20160123185308.39: *6* f.Repr
# Python 2.x only

def do_Repr(self, node):
    return 'repr(%s)' % self.visit(node.value)
#@+node:ekr.20160123185308.40: *6* f.Slice
def do_Slice(self, node):
    lower, upper, step = '', '', ''
    if getattr(node, 'lower', None) is not None:
        lower = self.visit(node.lower)
    if getattr(node, 'upper', None) is not None:
        upper = self.visit(node.upper)
    if getattr(node, 'step', None) is not None:
        step = self.visit(node.step)
    if step:
        return '%s:%s:%s' % (lower, upper, step)
    else:
        return '%s:%s' % (lower, upper)
#@+node:ekr.20160123185308.41: *6* f.Str
def do_Str(self, node):
    '''This represents a string constant.'''
    return repr(node.s)
#@+node:ekr.20160123185308.42: *6* f.Subscript
# Subscript(expr value, slice slice, expr_context ctx)

def do_Subscript(self, node):
    value = self.visit(node.value)
    the_slice = self.visit(node.slice)
    return '%s[%s]' % (value, the_slice)
#@+node:ekr.20160123185308.43: *6* f.Tuple
def do_Tuple(self, node):
    elts = [self.visit(z) for z in node.elts]
    return '(%s)' % ','.join(elts)
#@+node:ekr.20160123185308.44: *5* f.Operators
#@+node:ekr.20160123185308.45: *6* f.BinOp
def do_BinOp(self, node):
    return '%s%s%s' % (
        self.visit(node.left),
        self.op_name(node.op),
        self.visit(node.right))
#@+node:ekr.20160123185308.46: *6* f.BoolOp
def do_BoolOp(self, node):
    op_name = self.op_name(node.op)
    values = [self.visit(z) for z in node.values]
    return op_name.join(values)
#@+node:ekr.20160123185308.47: *6* f.Compare
def do_Compare(self, node):
    result = []
    lt = self.visit(node.left)
    # ops   = [self.visit(z) for z in node.ops]
    ops = [self.op_name(z) for z in node.ops]
    comps = [self.visit(z) for z in node.comparators]
    result.append(lt)
    if len(ops) == len(comps):
        for i in range(len(ops)):
            result.append('%s%s' % (ops[i], comps[i]))
    else:
        g.trace('ops', repr(ops), 'comparators', repr(comps))
    return ''.join(result)
#@+node:ekr.20160123185308.48: *6* f.UnaryOp
def do_UnaryOp(self, node):
    return '%s%s' % (
        self.op_name(node.op),
        self.visit(node.operand))
#@+node:ekr.20160123185308.49: *6* f.ifExp (ternary operator)
def do_IfExp(self, node):
    return '%s if %s else %s ' % (
        self.visit(node.body),
        self.visit(node.test),
        self.visit(node.orelse))
#@+node:ekr.20160123185308.50: *5* f.Statements
#@+node:ekr.20160123185308.51: *6* f.Assert
def do_Assert(self, node):
    test = self.visit(node.test)
    if getattr(node, 'msg', None):
        message = self.visit(node.msg)
        return self.indent('assert %s, %s' % (test, message))
    else:
        return self.indent('assert %s' % test)
#@+node:ekr.20160123185308.52: *6* f.Assign
def do_Assign(self, node):
    return self.indent('%s=%s\n' % (
        '='.join([self.visit(z) for z in node.targets]),
        self.visit(node.value)))
#@+node:ekr.20160123185308.53: *6* f.AugAssign
def do_AugAssign(self, node):
    return self.indent('%s%s=%s\n' % (
        self.visit(node.target),
        self.op_name(node.op), # Bug fix: 2013/03/08.
        self.visit(node.value)))
#@+node:ekr.20160123185308.54: *6* f.Break
def do_Break(self, node):
    return self.indent('break\n')
#@+node:ekr.20160123185308.55: *6* f.Continue
def do_Continue(self, node):
    return self.indent('continue\n')
#@+node:ekr.20160123185308.56: *6* f.Delete
def do_Delete(self, node):
    targets = [self.visit(z) for z in node.targets]
    return self.indent('del %s\n' % ','.join(targets))
#@+node:ekr.20160123185308.57: *6* f.ExceptHandler
def do_ExceptHandler(self, node):
    result = []
    result.append(self.indent('except'))
    if getattr(node, 'type', None):
        result.append(' %s' % self.visit(node.type))
    if getattr(node, 'name', None):
        if isinstance(node.name, ast.AST):
            result.append(' as %s' % self.visit(node.name))
        else:
            result.append(' as %s' % node.name) # Python 3.x.
    result.append(':\n')
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.58: *6* f.Exec
# Python 2.x only

def do_Exec(self, node):
    body = self.visit(node.body)
    args = [] # Globals before locals.
    if getattr(node, 'globals', None):
        args.append(self.visit(node.globals))
    if getattr(node, 'locals', None):
        args.append(self.visit(node.locals))
    if args:
        return self.indent('exec %s in %s\n' % (
            body, ','.join(args)))
    else:
        return self.indent('exec %s\n' % (body))
#@+node:ekr.20160123185308.59: *6* f.For
def do_For(self, node):
    result = []
    result.append(self.indent('for %s in %s:\n' % (
        self.visit(node.target),
        self.visit(node.iter))))
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    if node.orelse:
        result.append(self.indent('else:\n'))
        for z in node.orelse:
            self.level += 1
            result.append(self.visit(z))
            self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.60: *6* f.Global
def do_Global(self, node):
    return self.indent('global %s\n' % (
        ','.join(node.names)))
#@+node:ekr.20160123185308.61: *6* f.If
def do_If(self, node):
    result = []
    result.append(self.indent('if %s:\n' % (
        self.visit(node.test))))
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    if node.orelse:
        result.append(self.indent('else:\n'))
        for z in node.orelse:
            self.level += 1
            result.append(self.visit(z))
            self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.62: *6* f.Import & helper
def do_Import(self, node):
    names = []
    for fn, asname in self.get_import_names(node):
        if asname:
            names.append('%s as %s' % (fn, asname))
        else:
            names.append(fn)
    return self.indent('import %s\n' % (
        ','.join(names)))
#@+node:ekr.20160123185308.63: *7* f.get_import_names
def get_import_names(self, node):
    '''Return a list of the the full file names in the import statement.'''
    result = []
    for ast2 in node.names:
        if self.kind(ast2) == 'alias':
            data = ast2.name, ast2.asname
            result.append(data)
        else:
            g.trace('unsupported kind in Import.names list', self.kind(ast2))
    return result
#@+node:ekr.20160123185308.64: *6* f.ImportFrom
def do_ImportFrom(self, node):
    names = []
    for fn, asname in self.get_import_names(node):
        if asname:
            names.append('%s as %s' % (fn, asname))
        else:
            names.append(fn)
    return self.indent('from %s import %s\n' % (
        node.module,
        ','.join(names)))
#@+node:ekr.20160123185308.65: *6* f.Pass
def do_Pass(self, node):
    return self.indent('pass\n')
#@+node:ekr.20160123185308.66: *6* f.Print
# Python 2.x only

def do_Print(self, node):
    vals = []
    for z in node.values:
        vals.append(self.visit(z))
    if getattr(node, 'dest', None):
        vals.append('dest=%s' % self.visit(node.dest))
    if getattr(node, 'nl', None):
        # vals.append('nl=%s' % self.visit(node.nl))
        vals.append('nl=%s' % node.nl)
    return self.indent('print(%s)\n' % (
        ','.join(vals)))
#@+node:ekr.20160123185308.67: *6* f.Raise
def do_Raise(self, node):
    args = []
    for attr in ('type', 'inst', 'tback'):
        if getattr(node, attr, None) is not None:
            args.append(self.visit(getattr(node, attr)))
    if args:
        return self.indent('raise %s\n' % (
            ','.join(args)))
    else:
        return self.indent('raise\n')
#@+node:ekr.20160123185308.68: *6* f.Return
def do_Return(self, node):
    if node.value:
        return self.indent('return %s\n' % (
            self.visit(node.value)))
    else:
        return self.indent('return\n')
#@+node:ekr.20160123185308.69: *6* f.Suite
# def do_Suite(self,node):
    # for z in node.body:
        # s = self.visit(z)
#@+node:ekr.20160123185308.70: *6* f.TryExcept
def do_TryExcept(self, node):
    result = []
    result.append(self.indent('try:\n'))
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    if node.handlers:
        for z in node.handlers:
            result.append(self.visit(z))
    if node.orelse:
        result.append('else:\n')
        for z in node.orelse:
            self.level += 1
            result.append(self.visit(z))
            self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.71: *6* f.TryFinally
def do_TryFinally(self, node):
    result = []
    result.append(self.indent('try:\n'))
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    result.append(self.indent('finally:\n'))
    for z in node.finalbody:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.72: *6* f.While
def do_While(self, node):
    result = []
    result.append(self.indent('while %s:\n' % (
        self.visit(node.test))))
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    if node.orelse:
        result.append('else:\n')
        for z in node.orelse:
            self.level += 1
            result.append(self.visit(z))
            self.level -= 1
    return ''.join(result)
#@+node:ekr.20160123185308.73: *6* f.With
def do_With(self, node):
    result = []
    result.append(self.indent('with '))
    if hasattr(node, 'context_expression'):
        result.append(self.visit(node.context_expresssion))
    vars_list = []
    if hasattr(node, 'optional_vars'):
        try:
            for z in node.optional_vars:
                vars_list.append(self.visit(z))
        except TypeError: # Not iterable.
            vars_list.append(self.visit(node.optional_vars))
    result.append(','.join(vars_list))
    result.append(':\n')
    for z in node.body:
        self.level += 1
        result.append(self.visit(z))
        self.level -= 1
    result.append('\n')
    return ''.join(result)
#@+node:ekr.20160123185308.74: *6* f.Yield
def do_Yield(self, node):
    if getattr(node, 'value', None):
        return self.indent('yield %s\n' % (
            self.visit(node.value)))
    else:
        return self.indent('yield\n')
#@+node:ekr.20160123185308.75: *5* f.Utils
#@+node:ekr.20160123185308.76: *6* f.kind
def kind(self, node):
    '''Return the name of node's class.'''
    return node.__class__.__name__
#@+node:ekr.20160123185308.77: *6* f.indent
def indent(self, s):
    return '%s%s' % (' ' * 4 * self.level, s)
#@+node:ekr.20160123185308.78: *6* f.op_name
@nobeautify

def op_name (self,node,strict=True):
    '''Return the print name of an operator node.'''
    d = {
        # Binary operators. 
        'Add':       '+',
        'BitAnd':    '&',
        'BitOr':     '|',
        'BitXor':    '^',
        'Div':       '/',
        'FloorDiv':  '//',
        'LShift':    '<<',
        'Mod':       '%',
        'Mult':      '*',
        'Pow':       '**',
        'RShift':    '>>',
        'Sub':       '-',
        # Boolean operators.
        'And':   ' and ',
        'Or':    ' or ',
        # Comparison operators
        'Eq':    '==',
        'Gt':    '>',
        'GtE':   '>=',
        'In':    ' in ',
        'Is':    ' is ',
        'IsNot': ' is not ',
        'Lt':    '<',
        'LtE':   '<=',
        'NotEq': '!=',
        'NotIn': ' not in ',
        # Context operators.
        'AugLoad':  '<AugLoad>',
        'AugStore': '<AugStore>',
        'Del':      '<Del>',
        'Load':     '<Load>',
        'Param':    '<Param>',
        'Store':    '<Store>',
        # Unary operators.
        'Invert':   '~',
        'Not':      ' not ',
        'UAdd':     '+',
        'USub':     '-',
    }
    name = d.get(self.kind(node),'<%s>' % node.__class__.__name__)
    if strict: assert name,self.kind(node)
    return name
#@+node:ekr.20160123185308.79: *4* class StubFormatter (AstFormatter)
class StubFormatter (AstFormatter):
    @others
#@+node:ekr.20160123185308.80: *5* sf.Constants & Name
# Return generic markers allow better pattern matches.

def do_BoolOp(self, node): # Python 2.x only.
    return 'bool'

def do_Bytes(self, node): # Python 3.x only.
    return 'bytes' # return str(node.s)

def do_Name(self, node):
    return 'bool' if node.id in ('True', 'False') else node.id

def do_Num(self, node):
    return 'number' # return repr(node.n)

def do_Str(self, node):
    '''This represents a string constant.'''
    return 'str' # return repr(node.s)
#@+node:ekr.20160123185308.81: *4* class StubTraverser (ast.NodeVisitor)
class StubTraverser (ast.NodeVisitor):
    
    def __init__(self, c, d, output_fn):
        '''Ctor for StubTraverser class.'''
        self.c = c
        self.d = d
        self.format = StubFormatter().format
        self.in_function = False
        self.level = 0
        self.output_file = None
        self.output_fn = output_fn
        self.returns = set()

    @others
#@+node:ekr.20160123185308.82: *5* st.indent & out
def indent(self, s):
    '''Return s, properly indented.'''
    return '%s%s' % (' ' * 4 * self.level, s)

def out(self, s):
    '''Output the string to the console or the file.'''
    if self.output_file:
        self.output_file.write(self.indent(s)+'\n')
    else:
        print(self.indent(s))
#@+node:ekr.20160123185308.83: *5* st.run
def run(self, node):
    '''StubTraverser.run: write the stubs in node's tree to self.output_fn.'''
    c = self.c
    dir_ = g.os_path_dirname(self.output_fn)
    if g.os_path_exists(dir_):
        self.output_file = open(self.output_fn, 'w')
        aList = c.config.getData('stub-prefix')
        if aList:
            for z in aList:
                self.out(z.strip())
        self.visit(node)
        self.output_file.close()
        self.output_file = None
        g.es_print('wrote', self.output_fn)
    else:
        g.es_print('not found:', dir_)

#@+node:ekr.20160123185308.84: *5* st.visit (not used)
# This is needed only when subclassing from the leoAst.AstFullTraverser class.

# def visit(self, node):
    # '''Visit a *single* ast node.  Visitors are responsible for visiting children!'''
    # assert isinstance(node, ast.AST), node.__class__.__name__
    # method = getattr(self, 'do_' + node.__class__.__name__)
    # method(node)
#@+node:ekr.20160123185308.85: *5* st.Visitors
#@+node:ekr.20160123185308.86: *6* st.ClassDef
# ClassDef(identifier name, expr* bases, stmt* body, expr* decorator_list)

def visit_ClassDef(self, node):

    # Format...
    if not node.name.startswith('_'):
        if node.bases:
            s = '(%s)' % ','.join([self.format(z) for z in node.bases])
        else:
            s = ''
        self.out('class %s%s:' % (node.name, s))
    # Visit...
    self.level += 1
    old_in_function = self.in_function
    self.in_function = False
    for z in node.body:
        self.visit(z)
    self.level -= 1
    self.in_function = old_in_function
#@+node:ekr.20160123185308.87: *6* st.FunctionDef & helpers
# FunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list)

def visit_FunctionDef(self, node):
    
    # Do nothing if we are already in a function.
    # We do not generate stubs for inner defs.
    if self.in_function or node.name.startswith('_'):
        return
    # First, visit the function body.
    self.returns = set()
    self.in_function = True
    self.level += 1
    for z in node.body:
        self.visit(z)
    self.level -= 1
    self.in_function = False
    # Format *after* traversing
    self.out('def %s(%s) -> %s: ...' % (
        node.name,
        self.format_arguments(node.args),
        self.format_returns(node)))
#@+node:ekr.20160123185308.88: *7* format_arguments & helper
# arguments = (expr* args, identifier? vararg, identifier? kwarg, expr* defaults)

def format_arguments(self, node):
    '''
    Format the arguments node.
    Similar to AstFormat.do_arguments, but it is not a visitor!
    '''
    assert isinstance(node,ast.arguments), node
    args = [self.format(z) for z in node.args]
    defaults = [self.format(z) for z in node.defaults]
    # Assign default values to the last args.
    result = []
    n_plain = len(args) - len(defaults)
    # pylint: disable=consider-using-enumerate
    for i in range(len(args)):
        s = self.munge_arg(args[i])
        if i < n_plain:
            result.append(s)
        else:
            result.append('%s=%s' % (s, defaults[i - n_plain]))
    # Now add the vararg and kwarg args.
    name = getattr(node, 'vararg', None)
    if name: result.append('*' + name)
    name = getattr(node, 'kwarg', None)
    if name: result.append('**' + name)
    return ', '.join(result)
#@+node:ekr.20160123185308.89: *8* munge_arg
def munge_arg(self, s):
    '''Add an annotation for s if possible.'''
    a = self.d.get(s)
    return '%s: %s' % (s, a) if a else s
#@+node:ekr.20160123185308.90: *7* format_returns
def format_returns(self, node):
    '''Calculate the return type.'''
    def split(s):
        return '\n     ' + self.indent(s) if len(s) > 30 else s
        
    r = list(self.returns)
    r = [self.format(z) for z in r]
    # if r: g.trace(r)
    if len(r) == 0:
        return 'None'
    if len(r) == 1:
        return split(r[0])
    elif 'None' in r:
        r.remove('None')
        return split('Optional[%s]' % ', '.join(r))
    else:
        # return 'Any'
        s = ', '.join(r)
        if len(s) > 30:
            return ', '.join(['\n    ' + self.indent(z) for z in r])
        else:
            return split(', '.join(r))
#@+node:ekr.20160123185308.91: *6* st.Return
def visit_Return(self, node):

    self.returns.add(node.value)
#@+node:ekr.20111017085134.16114: *3* @button outlineToClipboard
result = []
firstLevel = p.level()

for p in p.self_and_subtree_iter():
    head = p.moreHead(firstLevel)
    head = g.toEncodedString(head,'ascii',reportErrors=True)
    result.append(head)
    body = p.moreBody() # Inserts escapes for '+' and '-' characters.
    if body:
        body = g.toEncodedString(body,'ascii',reportErrors=True)
        result.append(body)

result = '\n'.join(result) + '\n'
# print result
w = c.frame.body.bodyCtrl
w.clipboard_clear()
w.clipboard_append(result)
#@+node:ekr.20111017085134.16071: *3* @button pydoc
import os, sys, threading

python = sys.executable
pythonDir,junk = g.os_path_split(python)
pydoc = g.os_path_join(pythonDir,'Lib','pydoc.py')
command = '%s %s -g' % (python,pydoc)

# Execute the command in a separate thread.
def go():
    os.system(command)
    
threading.Thread(target=go).start()
#@+node:ekr.20150525161132.1: *3* @button tokens2tree
r'''The script to test AddTokensToTree class.'''
g.cls()
if c.isChanged():
    c.save()
<< imports >>
project = False
if project:
    aList = leoBeautify.ProjectUtils().project_files('leo')
    aList = ['@file %s' % (g.shortFileName(z).rstrip()) for z in aList]
    aList = aList
    settings_d = {}
else:
    aList = [
        'unit test: leoBeautify.py',
        # '@file leoFileCommands.py',
        # '@file leoBeautify.py',
    ]
    settings_d = {
        'ast_tokens_d': False,
        'stats': True,
        'input_string': True,
        'input_lines': False,
        'input_tokens': False,
        'token_list': False,
        'code_list': False,
        'output_string': True,
    }
assert aList,'no input in %s' % (p.h)
t1 = time.clock()
for h in aList:
    p2 = g.findNodeAnywhere(c,h)
    if p2:
        try:
            leoBeautify.test_LeoTidy(c,h,p2,settings_d)
        except Exception:
            g.es_exception()
if project:
    print('done: %4.2f sec.' % (time.clock()-t1))
#@+node:ekr.20150525161132.2: *4* << imports >>
# import leo.external.PythonTidy as PythonTidy
import leo.core.leoAst as leoAst
import leo.core.leoBeautify as leoBeautify
import imp
import time
imp.reload(leoAst)
imp.reload(leoBeautify)
# imp.reload(PythonTidy)
#@+node:ekr.20170301022829.1: *3* @ignore buttons new in Leo 5.5
# Please don't ignore these :-)
#@+node:ekr.20170301014329.1: *4* @button cycling syntax coloring
'''Cycle syntax coloring when there are multiple @langauge directives in a node.'''
# Original by Terry Brown, Revised by EKR.
while not p.isRoot():
    if p.b.strip().startswith("@language "):
        lines = g.splitLines(p.b)
        words = lines[0].split()
        # Careful: don't treat comments as languages.
        if len(words) > 2 and words[2][0].isalpha():
            # Cycle the languages on line 1.
            line0 = '%s %s %s\n' % (words[0], ' '.join(words[2:]), words[1])
            p.b = line0 + ''.join(lines[1:])
            c.selectVisBack()
            c.selectVisNext()
            break
    p.moveToParent()
else:
    g.es("No ambiguous @language directive found")
#@+node:ekr.20170211083757.1: *4* @button Demo @key=Ctrl-9
if c.isChanged(): c.save()
<< imports >>
<< class MyDemo >>
# Don't use @others here.
# The *same* command/key binding calls both demo-start and demo.next.
try:
    if getattr(g.app, 'demo', None):
        g.app.demo.next()
    else:
        g.cls()
        c.frame.log.clearTab('Log')
        g.es_print('Starting MyDemo')
        c.k.demoNextKey = c.k.strokeFromSetting('Ctrl-9')
            # Tell k.masterKeyHandler to process Ctrl-9 immediately.
            # Binding demo-next in a setting does *not* work.
        demo = MyDemo(c)
        p = g.findNodeAnywhere(c, '@button Demo @key=Ctrl-9')
        script_tree = g.findNodeInTree(c, p, 'demo-script')
        demo.start(script_tree, auto_run=True)
except Exception:
    g.app.demo = None
    raise
#@+node:ekr.20170211083757.2: *5* << imports >>
from leo.core.leoQt import QtGui
import leo.plugins.demo as demo_module
import imp
imp.reload(demo_module)
#@+node:ekr.20170211083757.3: *5* << class myDemo >>
class MyDemo (demo_module.Demo):
    
    @others
#@+node:ekr.20170211083757.4: *6* setup
def setup(self, p=None):
    c = self.c
    self.end_on_exception = True # Good for debugging.
    self.delta = 10
    demo.set_text_delta(10)
    self.geometry1 = self.get_top_geometry()
    p = g.findNodeAnywhere(c, 'Demo Area')
    self.root = p.copy() if p else None
    if p:
        p.expand()
        c.selectPosition(p)
    # c.frame.equalSizedPanes()
    c.redraw()
    self.set_youtube_position()
#@+node:ekr.20170211083757.5: *6* setup_script
def setup_script(self):
    self.delete_widgets()
#@+node:ekr.20170211083757.6: *6* teardown
def teardown(self):
    c = self.c
    self.delete_all_widgets()
    # self.set_top_geometry(self.geometry1)
    if hasattr(self, 'delta') and self.delta > 0:
        self.set_text_delta(-self.delta)
    if self.root and c.positionExists(self.root, trace=False):
        self.root.deleteAllChildren()
    p = c.lastTopLevel()
    p.expand()
    c.selectPosition(p)
    c.redraw()
#@+node:ekr.20170211083757.7: *6* teardown_script
def teardown_script(self):
    if self.auto_run:
        # Default wait.
        self.wait(0.5)
#@+node:ekr.20170211083757.8: *5* demo-script
@language python
#@+node:ekr.20170211083757.9: *6* First
Callout('Hello. This tutorial introduces Leo')
# Callout("First, I'll increase the text size for easier viewing")
# demo.next(wait=1.0)
#@+node:ekr.20170211083757.10: *6* Full featured outliner
# Create, move, promote, demote, hoist.
demo.retain(Title('Leo is a full featured outliner.'))
demo.wait(1.0)
###
demo.insert_node('a new node', keys=True, speed=10.0)
###
c.moveOutlineRight()
###
# demo.end() # Test of early exit.
###
demo.insert_node('another headline')
###
demo.insert_node('yet another node')
###
p = g.findNodeInTree(c, demo.root, 'a new node')
assert p, 'a new node'
c.selectPosition(p)
demo.wait(0.25)
###
c.demote()
demo.wait(1.0)
###
demo.delete_retained_widgets()
#@+node:ekr.20170211083757.11: *6* Leo's panes
# The body pane shows the text of the selected outline node.
#@+node:ekr.20170211083757.12: *6* Leo creates scripts from outlines
# Section refs, @others, @file
#@+node:ekr.20170211083757.13: *6* Clones and cff
#@+node:ekr.20170211083757.14: *6* Last
# Callout('Thanks for watching')
###
# demo.next()
#@+node:ekr.20161006092435.1: *4* @button import-tab-files
'''Import tab-indented files.'''
g.cls()
if c.isChanged():
    c.save()
separate = True # True: put all lines in separate nodes.
path = r'c:/test/tab_file_test.txt'
if 1:
    # Use the TabImporter class in leoImport.py.
    import leo.core.leoImport as leoImport
    importer = leoImport.TabImporter(c, separate=separate)
    importer.import_files([path])
else:
    # Use a custom class.
    @others
    s = open(path).read()
    last = c.lastTopLevel()
    root = last.insertAfter()
    root.h = path
    TabImporter(c, root=root, separate=separate).scan(s)
    g.es_print('Imported %s' % path)
    c.selectPosition(root)
    root.expand()
    c.redraw()
#@+node:ekr.20161006092435.2: *5* class TabImporter
class TabImporter:
    
    def __init__(self, c, root, separate):
        '''Ctor for the TabImporter class.'''
        self.c = c
        self.stack = []
        self.root = root
        self.separate = separate

    @others
#@+node:ekr.20161006092435.3: *6* check
def check(self, lines):
    '''Return False and warn if lines contains mixed leading tabs/blanks.'''
    blanks, tabs = 0, 0
    for s in lines:
        lws = self.lws(s)
        if '\t' in lws: tabs += 1
        if ' ' in lws: blanks += 1
    if tabs and blanks:
        g.es_print('intermixed leading blanks and tabs.')
        return False
    else:
        return True
#@+node:ekr.20161006092435.4: *6* dump_stack
def dump_stack(self):
    '''Dump the stack, containing (level, p) tuples.'''
    g.trace('==========')
    for i, data in enumerate(self.stack):
        level, p = data
        print('%2s %s %r' % (i, level, p.h))
#@+node:ekr.20161006092435.5: *6* lws
def lws(self, s):
    '''Return the length of the leading whitespace of s.'''
    for i, ch in enumerate(s):
        if ch not in ' \t':
            return s[:i]
    return s
    
    
#@+node:ekr.20161006092435.6: *6* scan
def scan(self, s1):
    trace = False and not g.unitTesting
    c, root, separate = self.c, self.root, self.separate
    if not s1.strip() or not root:
        return
    if trace: g.trace('importing to %s' % root.h)
    lines = g.splitLines(s1)
    if self.check(lines):
        self.stack = []
        for s in lines:
            if s.strip() or not separate:
                self.scan_helper(s)
#@+node:ekr.20161006092435.7: *6* scan_helper
def scan_helper(self, s):
    '''Update the stack as necessary and return (level, parent, stack).'''
    trace = False and not g.unitTesting
    root, separate, stack = self.root, self.separate, self.stack
    if stack:
        level, parent = stack[-1]
    else:
        level, parent = 0, None
    lws = len(self.lws(s))
    if trace:
        g.trace('----- level: %s lws: %s %s' % (level, lws, s.rstrip()))
    h = s.strip()
    if lws == level:
        if separate or not parent:
            # Replace the top of the stack with a new entry.
            if stack:
                stack.pop()
            grand_parent = stack[-1][1] if stack else root
            parent = grand_parent.insertAsLastChild() # lws == level
            parent.h = h
            stack.append((level, parent),)
        elif not parent.h:
            parent.h = h
    elif lws > level:
        # Create a new parent.
        level = lws
        parent = parent.insertAsLastChild()
        parent.h = h
        stack.append((level, parent),)
    else:
        # Find the previous parent.
        if trace: self.dump_stack()
        while stack:
            level2, parent2 = stack.pop()
            if level2 == lws:
                grand_parent = stack[-1][1] if stack else root
                parent = grand_parent.insertAsLastChild() # lws < level
                parent.h = h
                level = lws
                stack.append((level, parent),)
                break
        else:
            level = 0
            parent = root.insertAsLastChild()
            parent.h = h
            stack = [(0, parent),]
    if trace:
        g.trace('DONE: lws: %s level: %s parent: %s' % (lws, level, parent.h))
        self.dump_stack()
    assert parent and parent == stack[-1][1]
        # An important invariant.
    assert level == stack[-1][0], (level, stack[-1][0])
    if not separate:
        parent.b = parent.b + self.undent(level, s)
    return level
#@+node:ekr.20161006092435.8: *6* undent
def undent(self, level, s):
    '''Unindent all lines of p.b by level.'''
    # g.trace(level, s.rstrip())
    if level <= 0:
        return s
    if s.strip():
        lines = g.splitLines(s)
        ch = lines[0][0]
        assert ch in ' \t', repr(ch)
        # Check that all lines start with the proper lws.
        lws = ch * level
        for s in lines:
            if not s.startswith(lws):
                g.trace('bad indentation: %r' % s)
                return s
        return ''.join([z[len(lws):] for z in lines])
    else:
        return ''
#@+node:ekr.20170301023612.1: *4* @button insert headlines as comments
'''
    insert / update headlines as comments in @nosent R code
    https://gist.github.com/tbnorth/eb913fcab82f6a4b37734b5156543308
'''
# By Terry Brown
headlines = []
for nd in p.self_and_subtree():
    if nd.h and nd.h[0] == '@' or nd.b and nd.b[0] == '@':
        continue
    headlines.append(nd.h)
    lines = nd.b.split('\n')
    if lines and lines[0].startswith('### '):
        del lines[0]
    if lines and lines[0].strip():
        lines[0: 0] = [""]
    lines[0: 0] = [
        "### %s %s" % (nd.h, "#" * (80 - len(nd.h) - 5)),
    ]
    if '.coffee' in p.h:
        lines[0: 0] = [""]
    if lines[-1].strip():
        lines.append("")
    if lines[-2].strip():
        lines.append("")
    b = '\n'.join(lines)
    if nd.b != b:
        nd.b = b
g.es('\n'.join(headlines))
c.redraw()
#@+node:ekr.20170212105552.1: *4* @button IntroSlides @key=Ctrl-9
@language python
'''Create intro slides for screen shots.'''
# The *same* command/key binding calls both demo-start and demo.next.
if c.isChanged():
    c.save()
<< imports >>
#
# Do NOT use @others here.
#
<< class IntroSlides >>
<< main >>
if getattr(g.app, 'demo', None):
    g.app.demo.next()
else:
    demo = IntroSlides(c)
    main(c, demo,
        auto_run=False,
        hoist_node = "Leo's Main Window",
        script_name='intro-slides-script')
#@+node:ekr.20170212105552.2: *5* << imports >>
from leo.core.leoQt import QtGui
import leo.plugins.demo as demo_module
# import imp
# imp.reload(demo_module)
#@+node:ekr.20170212105552.3: *5* << class IntroSlides >>
class IntroSlides (demo_module.Demo):
    
    @others
#@+node:ekr.20170212105552.4: *6* setup
def setup(self, p=None):
    c = self.c
    self.end_on_exception = True # Good for debugging.
    self.delta = 0
    demo.set_text_delta(self.delta)
    # self.set_youtube_position()
    if hasattr(self, 'hoist_node'):
        c.selectPosition(self.hoist_node)
        c.hoist()
    c.redraw()
#@+node:ekr.20170212105552.5: *6* setup_script
def setup_script(self):
    self.delete_widgets()
#@+node:ekr.20170212105552.6: *6* teardown
def teardown(self):
    c = self.c
    self.delete_all_widgets()
    if self.delta > 0:
        self.set_text_delta(-self.delta)
    if self.hoist_node:
        c.selectPosition(self.hoist_node)
        c.dehoist()
    c.redraw()
#@+node:ekr.20170212105552.7: *6* teardown_script
def teardown_script(self):
    if self.auto_run:
        # Default wait.
        self.wait(0.5)
#@+node:ekr.20170212105552.8: *5* << main >>
def main(c, demo, script_name, auto_run=False, hoist_node=None):
    g.cls()
    k = c.k
    class_name = demo.__class__.__name__
    c.frame.log.clearTab('Log')
    g.es_print('Starting', class_name)
    k.demoNextKey = k.strokeFromSetting('Ctrl-9')
        # Tell k.masterKeyHandler to process Ctrl-9 immediately.
        # Binding demo-next in a setting does *not* work.
    h = '@button %s @key=Ctrl-9' % class_name
    p = g.findNodeAnywhere(c, h)
    assert p, h
    script_tree = g.findNodeInTree(c, p, script_name)
    assert script_tree, repr(script_name)
    demo.hoist_node = hoist_node and g.findNodeInTree(c, p, hoist_node)
    demo.start(script_tree, auto_run=auto_run)
#@+node:ekr.20170212105552.9: *5* Leo's Main Window
#@+node:ekr.20170212105552.10: *5* intro-slides-script
@language python
#@+node:ekr.20170212105552.11: *6* Slide 1: Leo's main window
Callout("This is Leo's main window")

###
demo.next()
#@+node:ekr.20160923132656.1: *4* @button introspect
@language python
"""Introspect"""

# By Terry Brown.  Requires Python 2.x.

# https://groups.google.com/forum/#!msg/leo-editor/Qu2HccpC_wc/_ee11jIvAQAJ

import types

sub_mode = 'instance'
# 'instance' or 'class' - controls which, instance or class names,
# are put it a subnode.  'instance class' sub-nodes both.
# '' appends classes after names, not useful.

def classname(thing):
    if hasattr(thing, '__class__'):
        return thing.__class__.__name__
    else:
        return thing.__name__

if not hasattr(c.p.v, '_introspection_target'):
    txt = g.app.gui.runAskOkCancelStringDialog(
        c, "Introspect what", "Introspect what")
    if txt is not None:
        o = eval(txt)
        c.p.v._introspection_target = o
        c.p.h = "%s %s" % (txt, classname(o))

# c.p.deletePositionsInList([i.copy() for i in p.children()])

obj = c.p.v._introspection_target
g.es(classname(obj))

def show_obj(c, obj):

    inames = sorted(dir(obj))
    
    things = {}
    instances = []
    for iname in inames:
        
        if iname.startswith('__'):
            continue
        
        o = getattr(obj, iname)
        cname = classname(o)
        instances.append((iname, o))
        things.setdefault(cname, []).append(instances[-1])

    if 'instance' in sub_mode:
        tnd = c.p.v.insertAsNthChild(0)
        tnd.h = "<by name>"
    else:
        tnd = c.p.v

    instances.sort()
    for iname, o in instances:
        
        if classname(o) == 'position':
            # apparently this collapses the space-time continuum?
            continue
        
        nd = tnd.insertAsLastChild()
        
        if not seen_already(tnd, nd, iname, o):
            nd.h = "%s %s" % (iname, format_type(nd, o))
            nd._introspection_target = o

    if 'class' in sub_mode:
        ttnd = c.p.v.insertAsNthChild(0)
        ttnd.h = "<by class>"
    else:
        ttnd = c.p.v

    for cname in sorted(things):
    
        if len(things[cname]) == 1:
            tnd = ttnd
        else:
            tnd = ttnd.insertAsLastChild()
            tnd.h = "<%s>"%cname
    
        for iname, o in sorted(things[cname]):
            
            if cname == 'position':
                # apparently this collapses the space-time continuum?
                continue
            
            nd = tnd.insertAsLastChild()
            if not seen_already(tnd, nd, iname, o):
                show_child(nd, iname, o)
                nd._introspection_target = o
         
def seen_already(tnd, nd, iname, o):
        
    up = tnd.parents
    while up:
        if (hasattr(up[0], '_introspection_target') and
            up[0]._introspection_target is o):
            break
        up = up[0].parents
    else:
        return False
        
    nd.h = "[%s %s]" % (classname(o), iname)
    pos = c.vnode2position(up[0])
    nd.b = pos.get_UNL(with_file=True, with_proto=True)
    
    return True
            
def show_child(nd, iname, o):
                
    nd._introspection_target = o
    nd.h = "%s %s" % (format_type(nd, o), iname)
    
docable = (
    types.ClassType, types.MethodType, types.UnboundMethodType, 
    types.BuiltinFunctionType, types.BuiltinMethodType,
)
    
def format_type(nd, o):
    
    if isinstance(o, docable):
        if hasattr(o, '__doc__'):
            nd.b = o.__doc__
    
    if isinstance(o, (str, unicode)):
        nd.b = o
        return "%s '%s'" % (classname(o), o[:20])
    elif isinstance(o, bool):
        return "%s %s" % (classname(o), 'T' if o else 'F')
    elif isinstance(o, (int, float)):
        return "%s %s" % (classname(o), o)
    elif isinstance(o, (tuple, list, dict)):
        return "%s %s" % (classname(o), len(o))
    else:
        return classname(o)
    
def show_list(c, list_):
    
    if len(list_) > 100:
        nd = c.p.v.insertAsLastChild()
        nd.h = "<%s of %d items truncated>" % len(list_.__class__.__name__, list_)
        
    if len(list_) == 0:
        nd = c.p.v.insertAsLastChild()
        nd.h = "<%s of 0 items>" % list_.__class__.__name__
        
    for n, i in enumerate(list_[:100]):
        nd = c.p.v.insertAsLastChild()
        show_child(nd, '', i)
        nd.h = "%d: %s" % (n, nd.h)
        nd._introspection_target = i

def show_dict(c, dict_):
    
    if len(dict_) > 100:
        nd = c.p.v.insertAsLastChild()
        nd.h = "<dict of %d items truncated>" % len(dict_)
        
    if len(dict_) == 0:
        nd = c.p.v.insertAsLastChild()
        nd.h = "<dict of 0 items>"
        
    keys = dict_.keys()
    keys.sort()
        
    for k in keys[:100]:
        nd = c.p.v.insertAsLastChild()
        i = dict_[k]
        show_child(nd, '', i)
        nd.h = "%s: %s" % (k, nd.h)
        nd._introspection_target = i

dispatch = {
    list: show_list,
    tuple: show_list,
    dict: show_dict,
}

func = dispatch.get(type(obj), show_obj)

func(c, obj)
   
c.p.expand()
c.redraw()
#@+node:ekr.20161031130627.1: *4* @button pep8 @key=Ctrl-5
'''
Undoably converts the word at the cursor to pep8 style throughout a given tree.
Also sets the find text to the new word.
'''
# aTestExample notFoundExample.
import re
# clear()
table = (
    'BLS.new_scan & helpers',
    'BLS.Code generation',
)
@others
Pep8(table, change=True).run()
#@+node:ekr.20161031130627.2: *5* class Pep8
class Pep8:
    '''
    Convert the word under the cursor to pep8 style in all subtrees in
    table.
    '''
    
    def __init__ (self, table, change=False):
        '''Ctor for Pep8 class.'''
        self.change = change
        self.table = table
        
    @others
#@+node:ekr.20161031130627.3: *6* change_all & helpers
def change_all(self, name, new_name, root):
    '''Undoably change name to new_name throughout root's tree.'''
    u = c.undoer
    bunch = u.beforeChangeTree(root)
    found = False
    self.pattern = re.compile(r'\b%s\b' % name)
    for p in root.self_and_subtree():
        found = self.change_headline(name, new_name, p) or found
        found = self.change_body(name, new_name, p) or found
    if found:
        u.afterChangeTree(root, 'pep8', bunch)
    return found
#@+node:ekr.20161031130627.4: *7* change_body
def change_body(self, name, new_name, p):
    indices = []
    for m in self.pattern.finditer(p.b):
        indices.append(str(m.start()))
    if indices:
        n = len(indices)
        g.es_print('%s change%s: %s' % (n, g.plural(n), p.h))
        s = p.b
        for i in reversed(indices):
            i = int(i)
            s = s[:i] + new_name + s[i+len(name):]
        if self.change:
            p.b = s
            p.setDirty()
        else:
            g.es_print(s)
    return bool(indices)
#@+node:ekr.20161031130627.5: *7* change_headline
def change_headline(self, name, new_name, p):
    m = self.pattern.search(p.h)
    if m:
        i = m.start()
        s = p.h
        s = s[:i] + new_name + s[i+len(name):]
        if self.change:
            p.h = s
            p.setDirty()
            g.es_print('changed headline', s)
        else:
            g.es_print('headline', s)
    return bool(m)
#@+node:ekr.20161031130627.6: *6* get_name
def get_name(self):
    i, j = c.editCommands.extendToWord(event=None, select=False)
    w = c.frame.body.wrapper
    s = w.getAllText()
    name = s[i:j]
    return name
#@+node:ekr.20161031130627.7: *6* run
def run(self):
    # self.clear()
    name = self.get_name()
    new_name = self.to_pep8(name)
    if len(name) < 2:
        g.es_print('name too short:', name)
    elif new_name == name:
        g.es_print('already pep8:', name)
    else:
        g.es_print('%s -> %s' % (name, new_name))
        c.findCommands.ftm.setFindText(new_name)
            # Preload the replacement text.
        found = False
        for target in table:
            root = g.findNodeAnywhere(c, target)
            assert root, target
            found = self.change_all(name, new_name, root) or found
        if found:
            c.redraw()
        else:
            g.es_print('not found:', name)
#@+node:ekr.20161031130627.8: *6* to_pep8
def to_pep8(self, s):
    
    if len(s) > 1 and s[0].islower() and s.lower() != s:
        result = []
        for ch in s:
            result.append(ch)
            if ch.isupper():
                result.pop()
                result.append('_%s' % (ch.lower()))
        return ''.join(result)
    else:
        return name
#@+node:ekr.20161031130627.9: *5* clear
def clear():
    g.cls()
    c.k.simulateCommand('clear-log')
#@+node:ekr.20170225023738.1: *4* @button test-http
'''
Web development involves endless reloading of pages.  It gets old really fast.

This @button allows me to fiddle with the .css and javascript for mod_http
itself without having to reload Leo. Oh so useful. I can just change the
settings in myLeoSettings and hit Ctrl-p to re-execute the button. 

This trick doesn't work when changing mod_http.py itself, but most of
the work involved the css and javascript.

This pattern could be useful to other web developers.
'''

# g.cls()
if c.isChanged():
    c.save()
# *Always* use the most current settings.
g.app.pluginsController.loadOnePlugin(
    'leo.plugins.mod_http',
    tag='open0',
    verbose=False)
g.app.loadManager.readGlobalSettingsFiles()
g.handleUrl('http://127.0.0.1:8130/leoPlugins.leo')
#@+node:ekr.20170120110015.1: *4* @ignore importer scripts
#@+node:ekr.20161120175101.1: *5* @button make-md-heads
'''
Create markdown headers throughout the nearest .md outline.

That is, prepend p.b[0] with markdown section markup, if appriate.
'''
g.cls()
import re
@others

def predicate(p):
    return p.isAnyAtFileNode() and p.h.strip().endswith('.md')

for root in c.all_unique_roots(predicate):
    print(root.h)
    for p in root.self_and_subtree():
        markup(p, root)
c.redraw()

@language python
@tabwidth -4
#@+node:ekr.20161120175101.2: *6* markup
pattern = re.compile(r'^(#+\s+)(.*)$')

def markup(p, root):
    '''prepend p.b[0] with markdown section markup, if appriate.'''
    root_level = root.level()
    lines = g.splitLines(p.b)
    if len(lines) < 2: return
    line0, line1 = lines[0], lines[1]
    if (
        not p.h.startswith('@md-ignore') and
        not line0.isspace() and # A real first line.
        not line0.startswith('@') and # Not a directive
        line1.isspace() # the next line is blank
    ):
        # Remove existing markup.
        m = pattern.match(line0)
        if m:  line0 = m.group(2) + '\n'
        # Add the correct markup.
        hashes = '#'*(p.level()-root_level+1)
        lines[0] = '%s %s' % (hashes, line0)
    # Ensure a blank line, so as not to interfere with later headers.
    s = ''.join(lines).rstrip()+'\n\n'
    if p.b != s:
        g.es_print('changed: %s' % (p.h))
        p.setDirty()
        c.setChanged(True)
        p.b = s
#@+node:ekr.20161123085419.1: *5* @button make-table
'''
Create a table of expected headlines in a unit test.

To use this script, select the root of the tree containing the expected
results. After running this, copy the table from the console.
'''
g.cls()
# Proper escapes are tricky.
table = [
    '"%s",' % p.h.replace('\\', '\\\\').replace('"', '\\"')
        for p in c.p.subtree()
]
print("table = (\n    %s\n)" % '\n    '.join(table))
#@+node:ekr.20161124034654.1: *5* @button make-importer
g.cls()
# define constants that describe the new language.
name = 'otl'
    # The name of the file, and the prefix for classes.
language = 'plain'
    # The name of the language, case doesn't matter.
extensions = ['.org',]
    # A list of file extensions supported by this importer.
strict = False
    # True if leading whitespace is particularly significant.
state_ivar = 'self.not_used'
    # 'self.indent' for python, coffeescript.
    # 'self.curlies' for many other languages
    # '(self, curlies, self.parens)' for more complex comparisons
<< define run & helpers >>
run(extensions, language, name, state_ivar)
#@+node:ekr.20161124034654.2: *6* << define run & helpers >>
@others
#@+node:ekr.20161124034654.3: *7* copy_tree
def copy_tree(source, root, h):
    '''Copy the source tree to the node after p, with headline h.'''
    p2 = root.insertAfter()
    source.copyTreeFromSelfTo(p2)
    p2.h = h
    return p2
 
#@+node:ekr.20161124034654.4: *7* make_substitutions
def make_substitutions(destination, patterns):
    '''Make all substitutions in the destination tree.'''
    for p in destination.self_and_subtree():
        h = substitute(p.h, patterns)
        if p.h != h:
            # g.trace('CHANGED:', p.h, '==>', h)
            p.h = h
        b = substitute(p.b, patterns)
        if p.b != b:
            # g.trace('CHANGED:', p.b, '==>', b)
            p.b = b
#@+node:ekr.20161124034654.5: *7* run
def run(extensions, language, name, state_ivar):
    '''The driver for this script.'''
    patterns = {
        'cap_name': name.capitalize(),
        'extensions': '[%s]' % ', '.join(["'%s'" % (z) for z in extensions]),
        'language': language.lower(),
        'name': name.lower(),
        'strict': 'True' if strict else 'False',
        'state_ivar': state_ivar,
    }
    h = '@button make-importer'
    root = g.findNodeAnywhere(c, h)
    assert root, h
    h = '@@file importers/{{name}}.py'
    source = g.findNodeInTree(c, root, h)
    assert source, h
    destination = copy_tree(source, root, h)
    make_substitutions(destination, patterns)
    c.contractAllHeadlines()
    c.redraw()
#@+node:ekr.20161124034654.6: *7* substitue
def substitute(s, patterns):
    '''Make all substitutions in s.'''
    for pattern in patterns:
        find = '{{%s}}' % pattern
        replace = patterns.get(pattern)
        i = 0
        while i < len(s):
            progress = i
            j = s.find(find, i)
            if j == -1: break
            s = s[:j] + replace + s[j+len(find):]
            i = j+len(replace)
            assert progress < i
    return s
#@+node:ekr.20161124034654.7: *6* @@file importers/{{name}}.py
'''The @auto importer for the {{name}} language.'''
import leo.plugins.importers.linescanner as linescanner
Importer = linescanner.Importer
@others
importer_dict = {
    'class': {{cap_name}}_Importer,
    'extensions': {{extensions}},
}
@language python
@tabwidth -4


#@+node:ekr.20161124034654.8: *7* class {{cap_name}}_Importer
class {{cap_name}}_Importer(Importer):
    '''The importer for the {{name}} lanuage.'''

    def __init__(self, importCommands, atAuto):
        '''{{cap_name}}_Importer.__init__'''
        # Init the base class.
        Importer.__init__(self,
            importCommands,
            atAuto = atAuto,
            language = '{{language}}',
            state_class = {{cap_name}}_ScanState,
            strict = {{strict}},
        )
        
    @others
#@+node:ekr.20161124034654.9: *8* {{name}}.Overrides
# These can be overridden in subclasses.
#@+node:ekr.20161124034654.10: *9* {{name}}.clean_headline
### Define an override if desired...

if 0: # The base class
    def clean_headline(self, s):
        '''Return a cleaned up headline s.'''
        return s.strip()
        
if 0: # A more complex example, for the C language.
    def clean_headline(self, s):
        '''Return a cleaned up headline s.'''
        import re
        type1 = r'(static|extern)*'
        type2 = r'(void|int|float|double|char)*'
        class_pattern = r'\s*(%s)\s*class\s+(\w+)' % (type1)
        pattern = r'\s*(%s)\s*(%s)\s*(\w+)' % (type1, type2)
        m = re.match(class_pattern, s)
        if m:
            prefix1 = '%s ' % (m.group(1)) if m.group(1) else ''
            return '%sclass %s' % (prefix1, m.group(2))
        m = re.match(pattern, s)
        if m:
            prefix1 = '%s ' % (m.group(1)) if m.group(1) else ''
            prefix2 = '%s ' % (m.group(2)) if m.group(2) else ''
            h = m.group(3) or '<no c function name>'
            return '%s%s%s' % (prefix1, prefix2, h)
        else:
            return s
#@+node:ekr.20161124034654.11: *9* {{name}}.clean_nodes
def clean_nodes(self, parent):
    '''
    Clean all nodes in parent's tree.
    Subclasses override this as desired.
    See perl_i.clean_nodes for an examplle.
    '''
    pass
#@+node:ekr.20161124034654.12: *7* class class {{cap_name}}_ScanState
class {{cap_name}}_ScanState:
    '''A class representing the state of the {{name}} line-oriented scan.'''
    
    def __init__(self, d=None):
        '''{{cap_name}}_ScanState.__init__'''
        if d:
            prev = d.get('prev')
            self.context = prev.context
            ### Adjust these by hand.
            self.curlies = prev.curlies
        else:
            self.context = ''
            ### Adjust these by hand.
            self.curlies = 0

    def __repr__(self):
        '''{{cap_name}}_ScanState.__repr__'''
        ### Adjust these by hand.
        return "{{cap_name}}_ScanState context: %r curlies: %s" % (
            self.context, self.curlies)

    __str__ = __repr__

    @others
#@+node:ekr.20161124034654.13: *8* {{name}}_state.level
def level(self):
    '''{{cap_name}}_ScanState.level.'''
    return {{state_ivar}}

#@+node:ekr.20161124034654.14: *8* {{name}}_state.update
def update(self, data):
    '''
    {{cap_name}}_ScanState.update

    Update the state using the 6-tuple returned by v2_scan_line.
    Return i = data[1]
    '''
    context, i, delta_c, delta_p, delta_s, bs_nl = data
    # All ScanState classes must have a context ivar.
    self.context = context
    self.curlies += delta_c  
    ### Update {{cap_name}}_ScanState ivars
    # self.bs_nl = bs_nl
    # self.parens += delta_p
    # self.squares += delta_s
    return i
#@+node:ekr.20161124133327.1: *5* @button make-test
'''Make an @test node for the importer whose name is given.'''
name = 'javascript'
d = g.app.permanentScriptDict
tag = 'make_test_%s_n' % name
n = d.get(tag, 0) + 1
d [tag] = n
d = {'n': n, 'name': name}
<< define preamble >>
p = c.lastTopLevel().insertAfter()
p.b = preamble
p.h = '@test %(name)s importer-%(n)s' % d
c.redraw()
c.selectPosition(p)
c.bodyWantsFocusNow()
c.frame.body.wrapper.setInsertPoint(len(p.b))
#@+node:ekr.20161124133327.2: *6* << define preamble >>
preamble = '''\
if 1:
    # The preamble...
    g.cls()
    if c.isChanged(): c.save()
    import imp
    import leo.plugins.importers.linescanner as linescanner
    import leo.plugins.importers.%(name)s as %(name)s
    imp.reload(linescanner)
    imp.reload(%(name)s)
# insert test for %(name)s here.
''' % d
#@+node:ekr.20161204063803.1: *5* @button make-md-toc
'''Make a markdown table of contents from an @auto-md node.'''
g.cls()
if c.isChanged(): c.save()

class Controller:
    '''Controller class for @button md-toc.'''
    @others
    
if 0:
    h = '@auto-md importers.md'
    p = g.findTopLevelNode(c, h)
    assert p, h
else:
    p = c.p
Controller().run(p)
@language python
@tabwidth -4
#@+node:ekr.20161204063803.2: *6* run
def run(self, p):
    '''The driver for @button md-toc.'''
    if p.h.startswith('@auto-md '): #  or p.h.endswith('.md'):
        last = c.lastTopLevel()
        self.root = last.insertAfter()
        self.root.h = 'Table of contents'
        self.make_toc(p)
    else:
        print('must be an @auto-md node or an x.md node: %s' % p.h)
#@+node:ekr.20161204063803.3: *6* mak_link
def make_link(self, s):
    '''Return the markdown link for s.'''
    result = []
    for ch in s.lower():
        if ch in ' -':
            result.append('-')
        elif ch.isalnum():
            result.append(ch)
        else:
            pass
    return ''.join(result)
#@+node:ekr.20161204063803.4: *6* make_toc
def make_toc(self, p):
    '''Create the toc in self.root.b.'''
    result, stack = [], []
    prefix = p.h.lstrip('@auto-md').strip()
    for p in p.subtree():
        level = p.level() - self.root.level()
        assert level > 0
        if len(stack) < level:
            stack.append(1)
        else:
            stack = stack[:level]
        n = stack[-1]
        stack[-1] = n+1
        indent = ' '*4*(level-1)
        line = '%s%s. [%s](%s#%s)\n' % (
            indent, n, p.h, prefix, self.make_link(p.h))
        result.append(line)
    if 0:
        g.trace(p.h)
        g.printList(result)
    else:
        self.root.b = ''.join(result)
    c.redraw()
#@+node:ekr.20181019042717.1: *3* @ignore at-button nodes in LeoSettings.leo
#@+node:ekr.20181019042907.1: *4* @button check-bindings
'''Check for commands that exist in some but not all key- binding sets.'''
g.cls()
import leo.core.leoConfig as leoConfig
# import leo.core.leoTest as leoTest
parser = leoConfig.SettingsTreeParser(c)
# Add these as required to handle commands defined by plugins.
optionalCommandPrefixes = ['group-operations']
optionalCommandNames = [
    # These are the command names as defined by plugins.
    # LeoSlideShows.leo defines buttons whose commands do not end in '-command'.
    'next-slide-command','next-slide-show-command',
    'prev-slide-command','prev-slide-show-command',
    # The ipython plugin.
    'start-ipython','get-ipython-results','push-to-ipython',
    # The viewrendered plugin.
    'vr-toggle',
]
setNames = []
setsDict = {} # keys are set names, values are dicts of command names.
shortcutsDict = {}
@others
main()
#@+node:ekr.20181019042907.2: *5* defineSetNames
def defineSetNames():
    global setNames
    if 1:
        setNames = []
        for p in c.allNodes_iter():
            h = p.headString()
            if h.startswith('@keys'):
                h = h[5:].strip()
                if h not in setNames:
                    setNames.append(h)
        g.es('Found these sets...')
        for setName in setNames:
            g.es_print('  %s' % str(setName))
    else:
        setNames = [
            'Default Emacs shortcuts',
            'Legacy Leo bindings',
            'Legacy Leo shortcuts with important Emacs bindings',
            'No bindings',
            'EKR bindings: a mix',
            'EKR bindings: Mode-oriented',
        ]

    setNames.sort()
#@+node:ekr.20181019042907.3: *5* doSet
def doSet(p,name):

    global shortcutsDict

    shortcutsDict = {}

    for p in p.subtree_iter():
        if p.headString().startswith('@shortcuts'):
            doShortcuts(p,name)
#@+node:ekr.20181019042907.4: *5* doShortcuts
def doShortcuts(p,setName):

    global parser,setsDict,shortcutsDict

    d = setsDict.get(setName,{})
    s = p.bodyString()
    lines = g.splitLines(s)
    for line in lines:
        line = line.strip()
        if line and not line.startswith('#'):
            commandName,si = parser.parseShortcutLine('test',line)
            if not si:
                g.es_print('In %s:\nmissing "=" in shortcut line:%s' % (
                    p.headString(),repr(line)),color='red')
            else:
                # Having multiple shortcuts for a command if fine,
                # But having multiple commands for a shortcut is not.
                shortcut = si.stroke
                pane = si.pane
                if shortcut not in (None,'None','none'):
                    aList = shortcutsDict.get(shortcut,[])
                    if aList:
                        for commandName2,pane2 in aList:
                            if pane == pane2:
                                g.es_print('duplicate shortcut %s in set %s: previous command: %s' % (
                                    shortcut,setName,commandName2),color='red')
                        else:
                            aList.append((commandName,pane),)
                    else:
                        shortcutsDict [shortcut] = [(commandName,pane),]

                data = d.get(commandName)
                if data:
                    shortcut2,pane2 = data
                    if shortcut == shortcut2 and pane == pane2:
                        g.es_print('duplicate %s in set %s' % (
                            commandName,setName),color='red')
                else:
                    data = shortcut,pane
                    d[commandName] = data

    setsDict[setName] = d
#@+node:ekr.20181019042907.5: *5* checkSets
def checkSets():

    global setNames, optionalCommandPrefixes, optionalCommandNames
    # Compute the union of all the command names.
    allNames = {}
    for setName in setNames:
        d = setsDict.get(setName)
        if d:
            for key in d.keys():
                allNames[key] = key
        else:
            g.es_print('No setsDict for %s' % (repr(setName)),color='red')
    keys = sorted(allNames.keys())
    # Warn about missing names.
    for setName in ('No bindings',): # setNames:
        d = setsDict.get(setName)
        if d:
            for key in keys:
                if key not in ('none','None',None) and key not in d.keys():
                    # Don't warn about missing 'enter-xxx-mode' commands.
                    if (
                        not (key.startswith('enter-') and key.endswith('-mode')) and
                        not (key.startswith('press-') and key.endswith('-button'))
                    ):
                        g.es_print('%s is missing %-35s = None' % (setName,repr(key)))
        else:
            g.es_print("'@keys No bindings' not found",color='blue')
    # Warn about undefined commands.
    for key in keys:
        if not c.commandsDict.get(key):
            ok = False
            # full-command and quick command are weird special cases.
            if key not in ('None',None,'full-command','quick-command'):
                # Don't warn about missing 'enter-xxx-mode' commands.
                if key.startswith('enter-') and key.endswith('-mode'):
                    ok = True
                elif key.startswith('press-') and key.endswith('-button'):
                    ok = True
                for prefix in optionalCommandPrefixes:
                    if key.startswith(prefix):
                        ok = True
                for optionalCommand in optionalCommandNames:
                    if key == optionalCommand:
                        ok = True
                if not ok:
                    g.es_print('Undefined command name: %s' % (key))
#@+node:ekr.20181019042907.6: *5* main
def main ():

    global setNames
    defineSetNames()

    g.es_print('-' * 40)
    seen = {}
    for p in c.allNodes_iter():
        h = p.headString()
        if h.startswith('@keys'):
            h = h[5:].strip()
            if not seen.get(h):
                seen[h] = True
                doSet(p,h)
    checkSets()
    g.es('Check Bindings done')
#@+node:ekr.20181019042834.1: *4* @button check-menus-cmds
'''Check that all commands mentioned in the @menus tree actually exist.'''

def checkMenu (p):
    for p2 in p.subtree_iter():
        if p2.h.startswith('@item'):
            checkItem(p.h,p2)

def checkItem (menuName,p):
    h = p.h[len('@item'):].replace('&','').replace('*','').strip()
    if h != '-' and h not in c.commandsDict:
        print ('command not found: %s: %s' % (menuName,p.h))

menus = g.findNodeAnywhere(c,'@menus')
assert menus, 'no @menus tree'
for p in menus.subtree_iter():
    if p.h.startswith('@menu'):
        checkMenu(p.copy())

print ('done')
#@+node:ekr.20181019042808.1: *4* @button check-settings
'''Check the consistency of all settings.'''
# https://github.com/leo-editor/leo-editor/issues/993

class Controller:
    @others

Controller(c).run()
#@+node:ekr.20181019042808.2: *5* ctor
def __init__(self, c):
    self.c = c
    self.errors = 0
    # Commanders...
    self.core = None # leoPy.leo.
    self.plugins = None # leoPlugins.leo.
    self.settings = None # leoSettings.leo.
#@+node:ekr.20181019042808.3: *5* check & helpers
def check(self, configs_d, settings_d):
    munge = self.munge
    table = ('Bool', 'Int', 'Float', 'Ratio', 'Path', 'String',) # 'Color', 'Font',
    #
    # Print missing user settings...
    for kind in table:
        config_key = 'get%s' % kind
        settings_key = '@%s' % kind.lower()
        configs = configs_d.get(config_key, [])
        settings = settings_d.get(settings_key, [])
        m_configs = [munge(z) for z in configs]
        m_settings = [munge(z) for z in settings]
        missing = set([z for z in m_configs if not z in m_settings])
        aList = [z for z in missing if self.filter_user_config(z)]
        if aList:
            print('\nmissing %s %s settings...\n' % (len(aList), settings_key))
            for z in sorted(aList):
                aList2 = [z2 for z2 in configs if munge(z2) == munge(z)]
                g.printObj(aList2)
    #
    # Print missing calls to c.config.getX...
    for kind in table:
        config_key = 'get%s' % kind
        settings_key = '@%s' % kind.lower()
        configs = configs_d.get(config_key, [])
        settings = settings_d.get(settings_key, [])
        m_configs = [munge(z) for z in configs]
        m_settings = [munge(z) for z in settings]
        missing = set([z for z in m_settings if not z in m_configs])
        aList = [z for z in missing if self.filter_get_x(z)]
        if aList:
            print('\nmissing %s config.%s calls...\n' % (len(aList), config_key))
            for z in sorted(aList):
                aList2 = [z2 for z2 in settings if munge(z2) == munge(z)]
                g.printObj(aList2)
#@+node:ekr.20181019042808.4: *6* filter_get_x
def filter_get_x(self, setting):
    '''
    Return False if we can safely ignore a missing call to config.get(setting).
    
    Everything here is a hack. Some are bigger than others.
    '''
    munge = self.munge
    #
    # These *ivars* are set by the GlobalConfigManager class.
    # There *should* be settings for all of these, despite missing config.get calls.
    table = (
        # encodingIvarsDict...
            "default_at_auto_file_encoding",
            "default_derived_file_encoding",
            "new_leo_file_encoding",
        # defaultsDict...
            "color_directives_in_plain_text",
            "output_doc_chunks",
            "page_width",
            "tab_width",
            "tangle_outputs_header",
            "target_language",
            "underline_undefined_section_names",
        # ivarsDict
            "at_root_bodies_start_in_doc_mode",
            "create_nonexistent_directories",
            "output_initial_comment",
            "output_newline",
            "page_width",
            "read_only",
            "redirect_execute_script_output_to_log_pane",
            "relative_path_base_directory",
            "remove_sentinels_extension",
            "save_clears_undo_buffer",
            "stylesheet",
            "tab_width",
            "target_language",
            "trailing_body_newlines",
            "use_plugins",
            "undo_granularity",
            "write_strips_blank_lines",
    )
    table = [munge(z) for z in table]
    if setting in table:
        return False
    #
    # unitTest.leo tests test-darwin-setting and test-win32-setting
    if setting in ('testdarwinsetting', 'testwin32setting'):
        return False
    #
    # Stylesheets use these settings.
    for pattern in (
        'bg', 'border', 'color', 'fg', 'font',
        'leogreen', 'leoyello',
        'margin', 'padding', 'relief',
        'solarized', 'split-bar', 'text-foreground', 'tree-image',
    ):
        if setting.find(munge(pattern)) > -1:
            return False
    #
    # These plugins/use settings in non-standard ways.
    if setting.startswith(
        ('bookmarks', 'datenodes', 'http', 'opml',
        'rst3', 'todo', 'vim', 'zodb'),
    ):
        return False
    #
    # Find settings are defined in non-standard ways.
    for pattern in (
        'batch', 'change-text', 'find-text', 'ignore-case',
        'mark-changes', 'mark-finds', 'node-only', 'pattern-match',
        'reverse', 'search-body', 'search-headline', 'suboutline-only',
        'whole-word', 'wrap',
    ):
        if setting == munge(pattern):
            return False
    #
    # Issue a warning.
    return True
#@+node:ekr.20181019042808.5: *6* filter_user_config
def filter_user_config(self, setting):
    '''
    Return False if we can safely ignore a setting that does not exist in leoSettings.leo.
    
    Everything here is a hack. Some are bigger than others.
    '''
    munge = self.munge
    #
    # unitTest.leo tests test-darwin-setting and test-win32-setting
    if setting in ('testdarwinsetting', 'testwin32setting'):
        return False
    #
    # The calls to config.get* are commented out in the code,
    # but get_configs isn't smart enough to know that.
    for ignore in (
        'auto-set-ignore-case',
        'find-def-creates-clones',
        'qt-rich-text-zoom-in',
        'theme-name',
        'pytest-path', # In a (disabled) @button node
        'leo-to-html-%s', # Loads multiple settings from an .ini file.
    ):
        if setting == munge(ignore):
            return False
    #
    # Stylesheets use these settings.
    # It would be a major project to discover what settings
    # are actually used in the present stylesheet.
    for pattern in (
        'bg', 'border', 'color', 'fg', 'font',
        'leogreen', 'leoyello',
        'margin', 'padding', 'relief',
        'solarized', 'split-bar', 'text-foreground', 'tree-image',
    ):
        if setting.find(munge(pattern)) > -1:
            return False
    #
    # These plugins use settings in non-standard ways.
    if setting.startswith(
        ('activepath', 'bookmarks', 'datenodes', 'http', 'opml',
        'rst3', 'todo', 'vim', 'zodb'),
    ):
        return False
    #
    # Find settings are defined in non-standard ways.
    for pattern in (
        'batch', 'change-text', 'find-text', 'ignore-case',
        'mark-changes', 'mark-finds', 'node-only', 'pattern-match',
        'reverse', 'search-body', 'search-headline', 'suboutline-only',
        'whole-word', 'wrap',
    ):
        if setting == munge(pattern):
            return False
    #
    # Tangle/untagle settings are deprecated and imo should not exist.
    for pattern in (
        'output-doc-flag', 'tangle-batch-flag',
        'untangle-batch-flag', 'use-header-flag',
    ):
        if setting == munge(pattern):
            return False
    #
    # Issue a warning.
    return True
#@+node:ekr.20181019042808.6: *5* error
def error(self, s):
    print(s)
    self.errors += 1
#@+node:ekr.20181019042808.7: *5* get_commanders
def get_commanders(self):
    '''Open files as needed and set the commander ivars.'''

    def open_commander(fn):
        c = g.openWithFileName(fn, old_c=self.c, gui=g.app.nullGui)
        if not c:
            self.error('not found: %s' % fn)
        return c

    join, loadDir = g.os_path_join, g.app.loadDir
    self.core = open_commander(join(loadDir, '..', 'core', 'leoPy.leo'))
        # Opening LeoPyRef.leo would be slower.
    self.plugins = open_commander(join(loadDir, '..', 'plugins', 'leoPlugins.leo'))
    self.settings = open_commander(join(loadDir, '..', 'config', 'leoSettings.leo'))
#@+node:ekr.20181019042808.8: *5* get_configs & helpers
def get_configs(self):
    '''
    Return a dict containing a representation of all calls to x.config.getX.
    '''
    d = {}
    for c in (self.core, self.plugins):
        print('scanning: %s' % c.shortFileName())
        self.get_configs_from_outline(c, d)
    return d
#@+node:ekr.20181019042808.9: *6* get_configs_from_outline & helper
def get_configs_from_outline(self, c, d):
    '''
    Scan the outline for all calls to x.config.getX and add items to d.
    '''
    for p in c.all_positions():
        self.scan_for_configs(p, d)
    return d
#@+node:ekr.20181019042808.10: *7* scan_for_configs
def scan_for_configs(self, p, d):
    '''
    Scan the body text of p, finding all calls to config.getX.
    
    This code does not know about `if 0`, but does know about comments.
    '''
    kinds = (
        'getBool', 'getColor', 'getInt', 'getFloat',
        # '@font', # special case.
        'getPath', 'getRatio', 'getString',
    )
    i, s = 0, p.b
    while i < len(s):
        progress = i
        ch = s[i]
        if (
            ch == '@' and
            (g.match(s, i, '@ ') or g.match(s, i, '@\n')) and
            (i == 0 or s[i - 1] == '\n')
        ):
            # Skip the @doc part.
            i = s.find('\n@c', i)
            if i == -1: break
        elif ch == '#':
            i = g.skip_to_end_of_line(s, i)
        elif ch in ('"', "'"):
            i = g.skip_python_string(s, i, verbose=False)
        elif ch == '_' or ch.isalpha():
            j = g.skip_id(s, i)
            kind = s[i: j]
            if kind in kinds:
                # We have found a call to getBool, etc.
                i = g.skip_ws(s, j)
                if g.match(s, i, '('):
                    i = g.skip_ws(s, i + 1)
                    if g.match(s, i, '"') or g.match(s, i, "'"):
                        j = g.skip_string(s, i)
                        name = s[i + 1: j - 1]
                        aList = d.get(kind, [])
                        if name not in aList:
                            aList.append(name)
                        d[kind] = aList
                else:
                    j = i
            i = j
        else:
            i += 1
        assert progress < i
    return d
#@+node:ekr.20181019042808.11: *5* get_settings & helper
def get_settings(self):
    '''Return a dict containing a representation
    of all settings in leoSettings.leo.
    '''
    trace = False
    c, d = self.settings, {}
    print('scanning: %s' % c.shortFileName())
    settings_node = g.findNodeAnywhere(c, '@settings')
    if not settings_node:
        return self.error('no @settings node')
    for p in settings_node.subtree():
        if self.is_setting(p):
            kind, name = self.parse_setting(p)
            if name:
                # name = self.munge(name)
                aList = d.get(kind, [])
                if name not in aList:
                    aList.append(name)
                d[kind] = aList
            else:
                self.error('no name for %s' % (kind))
    if trace:
        keys = list(d.keys())
        for key in sorted(keys):
            print(key)
            aList = d.get(key)
            for name in sorted(aList):
                print('  ' + name)
    return d
#@+node:ekr.20181019042808.12: *6* is_setting
def is_setting(self, p):
    # For now, these are enough
    table = (
        '@bool', '@color', '@int', '@float',
        # '@font', # special case.
        '@ratio', '@path', '@string',
    )
    for s in table:
        if g.match_word(p.h, 0, s):
            return True
    return False
#@+node:ekr.20181019042808.13: *6* parse_setting
def parse_setting(self, p):
    s = p.h
    assert s[0] == '@'
    i = g.skip_id(s, 1)
    kind = s[: i]
    assert kind
    i = g.skip_ws(s, i)
    j = g.skip_id(s, i, chars='-')
    name = s[i: j]
    return kind, name
#@+node:ekr.20181019042808.14: *5* munge
def munge(self, s):
    '''Return the canonicalized name for settings and arguments to c.config.getX.'''
    return g.toUnicode(s.replace('-', '').replace('_', '').lower())
#@+node:ekr.20181019042808.15: *5* run
def run(self):
    g.cls()
    self.get_commanders()
    configs = self.get_configs()
    settings = self.get_settings()
    if self.errors == 0:
        self.check(configs, settings)
    g.trace('done')
#@+node:ekr.20150416062553.1: ** Command-line scripts
#@+node:ekr.20130812034101.12558: *3* Add docutils to python3
@language python

@ Matt Wilkie <maphew@gmail.com>

Here is a recipe using the pip python installer, that adds docutils to
python 3 in about 5 minutes. Ideally the same template/process would be
extended for all of Leo, and wrapped up in a nice package.

Depends on win32 `curl.exe` being available,
http://curl.haxx.se/dlwiz/?type=bin&os=Win32&flav=-&ver=-

There are lots of scary looking warnings and messages emitted to the
console, mostly about unicode and files looked for and not found. A couple
of places I needed to tap [enter] (with no prompt saying that was
necessary). At the "install docutils" stage there was a long pause with
nothing apparent happening, perhaps 3 minutes.

The command shell was a generic windows cmd.exe shell with no python
variables set (e.g. PYTHONPATH, PYTHONHOME, etc.)

Recipe adapted from http://trac.osgeo.org/osgeo4w/wiki/ExternalPythonPackages
@c

pushd c:\\python32

:: test for docutils
python -c "import docutils; dir(docutils)"

::Traceback (most recent call last):
::  File "<string>", line 1, in <module>
::ImportError: No module named docutils

:: install python `distribute`
curl http://python-distribute.org/distribute_setup.py | python

:: install pip
curl --insecure https://raw.github.com/pypa/pip/master/contrib/get-pip.py |
python

::install docutils
.\\scripts\\pip.exe install docutils

:: test that docutils is available
python -c "import docutils; help(docutils)"

::Help on package docutils:
::
::NAME
::    docutils - This is the Docutils (Python Documentation Utilities)
package.
::
::DESCRIPTION
::    Package Structure
::    =================
::: ...snip...
#@+node:ekr.20110301092139.14873: *3* Terry Brown's command-line scripts
@language python
#@+node:ekr.20110301092139.14874: *4* docs
@language rest

Two use cases spring to mind:

Leo is already running (somewhere on my desktop) with 2-3 leo files loaded in
tabs. I'm working in a console, and I want to edit a file in the current
directory: `led foo.py` causes the running leo to create '@edit foo.py' and pop
to the front with it loaded. Vs. finding the running version of leo, then
navigating through the open file dialog to the directory I'm already in in the
shell window.

And of course with the new sticknotes plugin, set up a window manager hotkey to
pop open a new stickynote from the running leo to jot something down.

======

Since I'm making it a two-way protocol (you can execute scripts in leo, and get
results back), any kind of command line application will be possible. Something
like having leo operations in shell pipeline or shell scripts will make sense.

e.g. you could do

make | leopaste "compilation results for tuesday"

Or

leocat | grep "frobbo"

which could allow you to navigate around leo outline and press some
kind of "paste" button to dump the current node to stdout.

These are somewhat psychedelic for now, but lots of Leo is about
imagination ;-).


#@+node:ekr.20110301092139.14875: *5* sn
You can type(*), either in a shell window or the window manager's
command line entry area, "sn lisa from accounting", and a node called
"lisa from accounting" with an initial contents recording the current
time is created at the top of the leo commander which started the
server.  A stickynote window for editing that node pops up and becomes
active.  If you just type "sn" a default node name of the current time
is used.  Also a read-only attribute is set so the created date is
shown if you're running the edit_attribs.py plugin ;-)

(*) assuming you call the script 'sn' and put it on your path.
#@+node:ekr.20110301092139.14876: *5* led
Here is a quick hack at a script to edit (or create and edit) a file in leo
from the command line.

 - note the ugly sys.path.append, you don't need that line if you have
  leo installed, but I only run it from bzr

 - it attempts to find a previous editing of the file in leo, but it
  could easily miss it leading you to have multiple edits of the same
  file in leo, take care, or delete the @edit node when you're done
  editing

I've wanted to be able to pop up a file in leo like this for ages.  Now
this script, called 'led' and made executable and placed on the path,
will replace 'e', my emacs invocation script.

...which is interesting I guess, that what was keeping me from
replacing emacs almost completely with leo is not leo itself, but how
leo's invoked to edit a simple file from the command line.

Apart from nxml-mode, emacs's validating xml editing mode, I can't
think what I'll use emacs for now.
#@+node:ekr.20110301092139.14877: *5* led 2
It's probably of little benefit to people who work with mouse, menus,
and icons all the time.

But if you do everything from the command line (i.e. your OS's shell),
then it makes moving things into leo much smoother.

Suppose I've run leo and checked my todo items for the day, and now
leo's buried under some other window and I'm working in the shell in
some directory I've just created where I've just unzipped something and
now I want to edit a file that was in the .zip.

I can either
 - find the leo window
 - insert a node
 - active the open file dialog
 - navigate to the directory containing this file
 - select the file
 - and finally do the editing I want to do
or, with Ville's communication to the running leo
 - enter on the command line `led foo.txt`
 - and do the editing I want to do

where led is a script which causes the running leo to create an @edit
node containing foo.txt and pop to the front with the node selected.

Previously I was much more likely to use emacs, just because it was
easier to invoke that way from the command line.

So, opening files, creating sticky notes, invoking leo to handle output
from grep or diff or whatever - all these things are better now.

=====

The corresponding point and click process for this scenario is

a) select > r-click > Edit with Leo
b) or drag'n'drop from folder to Leo icon on task bar (or window if visible)

In short, I see this being a productivity boost for all users.
#@+node:ekr.20110301092139.14878: *4* led
#!/usr/bin/python

'''Edit a file in leo from the command line, adds an @edit node at the top of
the outline of the first commander in the leo instance.'''

import sys
sys.path.append("/home/tbrown/Desktop/Package/leo/bzr/leo.repo/trunk")
from leo.external import lproto
import os

addr = open(os.path.expanduser('~/.leo/leoserv_sockname')).read()
pc  = lproto.LProtoClient(addr)
pc.send("""
import os
fn = %s
c = g.app.commanders()[0]
h = "@edit "+fn
n = g.findNodeAnywhere(c, h)
if not n:
 n = c.rootPosition().insertAfter()
 n.moveToRoot(c.rootPosition())
 n.h = h
 if os.path.isfile(fn):
     n.b = file(fn).read()
c.selectPosition(n)
c.redraw()
c.bringToFront()
""" % repr(os.path.join(os.getcwd(), sys.argv[1])) )
#@+node:ekr.20110301092139.14879: *4* lo
#!/usr/bin/python

'''Load a .leo file from the command line into a running leo instance.'''

import sys
sys.path.append("/home/tbrown/Desktop/Package/leo/bzr/leo.repo/trunk")
from leo.external import lproto
import os

addr = open(os.path.expanduser('~/.leo/leoserv_sockname')).read()
pc  = lproto.LProtoClient(addr)
pc.send("""
c = g.app.commanders()[0]
g.openWithFileName(%s,old_c=c)
""" % repr(os.path.join(os.getcwd(), sys.argv[1])) )
#@+node:ekr.20110301092139.14880: *4* sn
#!/usr/bin/python

'''Sticky note tie-in from command line/toolbar icon to take quick notes using
a running leo.'''

import sys
sys.path.append("/home/tbrown/Desktop/Package/leo/bzr/leo.repo/trunk")
from leo.external import lproto
import os
import time
import sys

addr = open(os.path.expanduser('~/.leo/leoserv_sockname')).read()
pc  = lproto.LProtoClient(addr)
cmd="""
c = g.app.commanders()[0]
n = c.rootPosition().insertAfter()
n.moveToRoot(c.rootPosition())
n.h = "{timestamp}"
n.b = '''{content}\n\n# {timestamp}'''
c.selectPosition(n)
c.redraw()
c.k.simulateCommand('stickynote')
""".format(timestamp=time.asctime(), content=' '.join(sys.argv[1:]))
pc.send(cmd)
# print cmd
#@+node:ekr.20150416071458.1: ** Commands acting on nodes
#@+node:ekr.20060824181946: *3* @@button convert-to-at-shadow
"""
Look for @thin files in the current subtree.
Convert those thin files into a file with a shadow file,
if this shadow file does not exist already.

FIXME: the line end convention is currently changed:
      unix lineendings are converted to DOS lineendings,
      if files are converted on Windows.
      Not sure if that is a probem or not.
"""

# This script is deprecated because @shadow is deprecated.

import mod_shadow_core, os, shutil

def marker_from_extension(filename):
    marker = g.comment_delims_from_extension(filename)[0]
    return marker and marker + '@'

shadow_subdir = c.config.getString("shadow_subdir").strip()
if not shadow_subdir: assert False,'No shadow_subdir setting'
prefix = c.config.getString("shadow_prefix")

for p in p.self_and_subtree():
   h = p.h.strip()
   if h.startswith("@thin"):
       start = h.find("@thin") + len("@thin")
       leofiledir = os.path.split(c.mFileName)[0]
       filename = h[start:].strip()
       fullfilename = os.path.join(leofiledir, filename)
       theDir = os.path.split(fullfilename)[0]
       leoFolder = os.path.join(leofiledir, theDir, shadow_subdir)
       if not os.path.exists(leoFolder):
           os.mkdir(leoFolder)
           assert os.path.exists(leoFolder)
       else:
           assert os.path.isdir(leoFolder)
       junk, name = os.path.split(filename)
       newname = os.path.join(leoFolder, prefix + name)
       if os.path.exists(newname):
           continue
       g.es( "renaming %s to %s" % (filename, newname))
       shutil.copy2(fullfilename, newname)
       os.unlink(fullfilename)
       f = file(fullfilename, "w")
       f.close()
       mod_shadow_core.copy_file_removing_sentinels(
           sourcefilename=newname,
           targetfilename=fullfilename,
           marker_from_extension = marker_from_extension)
       g.es("File %s is now shadowed" % filename)
#@+node:ville.20090508224531.9799: *3* @@button Dump nodes to ~/.leo/dump
""" Dump nodes to ~/.leo/dump git repository.

Before using this, you need to:
    mkdir ~/.leo/dump; cd ~/.leo/dump; git init

"""

import os, codecs, hashlib
flatroot = os.path.expanduser('~/.leo/dump')
assert os.path.isdir(flatroot)

hl = []

def dump_nodes():
    for p in c.all_unique_positions():
        name, date, num = p.v.fileIndex
        gnx = '%s%s%s' % (name, date, num)
        hl.append('<a href="%s">%s%s</a><br/>' % (gnx, '-' * p.level(), p.h))
        fname = gnx
        codecs.open(fname,'w', encoding='utf-8').write(p.b)
        print "wrote", fname

os.chdir(flatroot)

dump_nodes()
lis = "\n".join(hl)

html = "<body>\n<tt>\n" + lis + "\n</tt></body>"

#titlename = c.frame.getTitle() + '.html'
pth, bname = os.path.split(c.mFileName)

if pth and bname:
    dbdirname = bname + "_" + hashlib.md5(c.mFileName).hexdigest()    

titlename = dbdirname + '.html'
codecs.open(titlename,'w', encoding='utf-8').write(html)

g.es("committing to " + flatroot)

os.system('git add *')
out = os.popen('git commit -m "Leo autocommit"').read()
g.es("committed")
g.es(out)
g.es('Outline in ' + os.path.abspath(titlename))
#@+node:ekr.20060531085804: *3* @@button Show other clones
@
Ever have a clone that is difficult to understand outside the context of its
original parent? Here's some code to help. It displays the headline of the
current node plus the headlines of all the parents of all the clones of the
current node. Selecting a displayed parent headline moves the current node to
the corresponding clone in the outline.

The idea is to be able to quickly see the context of all the clones of the
current node and to be able to easily navigate from one clone instance to the
next.
@c

@others
c.cn = cloneNavigator(c)
c.cn.displayClones(c)
#@+node:ekr.20060531085804.1: *4* class cloneNavigator
class cloneNavigator:
    '''
       Displays the headline of the current node plus the headlines of
       all the parents of all the clones of the current node.  Selecting
       a displayed parent headline moves the current node to the
       corresponding clone in the outline.

       The idea is to be able to quickly see the context of all the clones
       of the current node and to be able to easily navigate from one clone
       instance to the next.
    '''
    @others
#@+node:ekr.20060531085804.2: *5* init
def __init__ (self,c):
    self.c = c
    import Tkinter as Tk
    if 0:
        f = Tk.Toplevel()
    else:
        log = c.frame.log
        log.selectTab('Clones')
        f = log.tabFrame
        for w in f.winfo_children():
            w.destroy()

    # Create and pack empty label and listbox
    self.title = Tk.Label(f)
    self.title.pack(anchor="nw")
    self.lb = Tk.Listbox(f)
    self.lb.pack(expand=1,fill="both")
#@+node:ekr.20060531085804.3: *5* getAllClones
def getAllClones(self,p):
    c = self.c
    return [z.copy() for z in c.all_positions() if z.v == p.v]
#@+node:ekr.20060531085804.4: *5* displayClones
def displayClones(self,c):
    '''Displays the parent headline for all the clones of the current position'''
    cp = c.currentPosition()

    # "Title" is the headline of the current node
    self.title.configure(text=cp.h)

    # Initialize listbox and clone list
    clones = self.getAllClones(cp)
    self.lb.delete(0,self.lb.size()-1)

    <<Fill listbox with clone parent headlines>>    
    <<Goto selected position when listbox selection changes>>
#@+node:ekr.20060531085804.5: *6* <<Fill listbox with clone parent headlines>>
# Add the headlines of all the clone parents to the listbox
for p in clones:
    if p.parent():
        text = p.parent().h
    else:
        text = "<root>"
    self.lb.insert(self.lb.size(),text)

    # Initial listbox selection corresponds to current position
    if p.v == cp.v:
        self.lb.selection_set(self.lb.size()-1)
#@+node:ekr.20060531085804.6: *6* <<Goto selected position when listbox selection changes>>
# Callback for when a listbox entry is selected            
def gotoSelectedPosition(event,lb=self.lb,c=c,positions=clones):
    idx = int(lb.curselection()[0])
    p = positions[idx]
    c.frame.tree.expandAllAncestors(p)
    c.selectPosition(p)
    return
self.lb.bind(g.angleBrackets("ListboxSelect"), gotoSelectedPosition)
#@+node:ekr.20130810093044.16935: *3* Add @script node
'''
Adds a @script node to your outline which reloads the other outlines
currently loaded when this outline is next loaded.
'''

tablist = g.findNodeAnywhere(c, '@script load tabs')
if not tablist:
    from leo.core.leoNodes import vnode
    v = vnode(c)
    v.h = '@script load tabs'
    v._linkAsNthChild(c.hiddenRootNode,
         len(c.hiddenRootNode.children))
tablist = g.findNodeAnywhere(c, '@script load tabs')
assert tablist
import time
b = ["# Generated %s\n"%time.strftime('%c')]
for oc in g.app.commanders():
    b.append("g.openWithFileName('%s', c)" % oc.fileName())
b.append("c.frame.bringToFront()")
b.append("c.setLog()")
tablist.b = '\n'.join(b)
#@+node:tbrown.20141110113251.1: *3* Capture idea / ph. msg / to-do item
@g.command('x')
def add_note(args):
    """Create a global Alt-x command 'x' to quickly add a top level node
    to workbook.leo, if it's open, else the first open Leo outline, to
    quickly record a note / thought / to-do item.
    
    Marks the node as a priority 2 to-do item due today, titles the node
    with current date / time, and included date / time in body.
    """
    # search for workbook.leo
    for c in g.app.commanders():
        if c.fileName().endswith('workbook.leo'):
            break
    else:
        c = g.app.commanders()[0]  # or use first open outline
    
    # add a node a the top of the outline
    nd = c.rootPosition().insertAfter()
    nd.moveToRoot(c.rootPosition())
    
    # label the node and add to-do attributes
    import datetime, time
    nd.h = time.asctime()
    nd.b = "\n\n# %s\n\n" % nd.h 
    nd.v.u['annotate'] = {
        'duedate': datetime.date.today(),
        'created': datetime.datetime.now(),
        'nextworkdate': datetime.date.today(),
        'priority': 2,
    }

    # select the node and focus in body for immediate typing
    c.selectPosition(nd)
    c.cleo.loadIcons(nd)
    c.redraw()
    c.bringToFront()
    c.bodyWantsFocusNow()
#@+node:ekr.20120602062004.12350: *3* Clean imported nodes
'''A script to clean Python imports.
'''

h = 'Coverage (live)'
    # The headline of the tree to be converted.

class CleanPython:
    def __init__ (self):
        self.trace = True
    @others

p = g.findNodeAnywhere(c,h)
if p:
    CleanPython().run(c,p)
else:
    print('not found: %s' % (h))
#@+node:ekr.20120602062004.12351: *4* run
def run(self,c,p):

    if self.trace: g.cls()
    
    self.changed = 0
    p1 = p.copy()
    
    # Don't set any nodes dirty here.
    for p in p1.self_and_subtree():
        if p.isMarked():
            p.v.clearMarked()
            
    bunch = c.undoer.beforeChangeTree(p1)
   
    for p in p1.children():
        if self.trace: print('\n***** %s' % (p.h))
        self.clean(c,p)
        
    if self.changed:
        c.undoer.afterChangeTree(p1,'clean-python-code',bunch)
        c.redraw()

    g.trace('done: %s nodes changed' % (self.changed))
#@+node:ekr.20120602062004.12352: *4* clean & helpers
def clean(self,c,p):
    
    '''
    - Move a shebang line from the first child to the root.
    - Move a leading docstring in the first child to the root.
    - Use a section reference for declarations.
    - Remove leading and trailing blank lines from all nodes.
    - Merge a node containing nothing but comments with the next node.
    - Merge a node containing no class or def lines with the previous node.
    '''

    root = p.copy()
    assert p.h.startswith('@@file') or p.h.startswith('@file'),p.h
    
    self.move_shebang_line(c,root)
    self.move_doc_string(c,root)
    self.rename_decls(c,root)

    for p in root.self_and_subtree():
        self.clean_blank_lines(c,p)
    for p in root.subtree():
        self.merge_comment_nodes(c,p)
    for p in root.subtree():
        self.merge_extra_nodes(c,p)
#@+node:ekr.20120602062004.12353: *5* move_shebang_line
def move_shebang_line (self,c,root):
    
    '''Move a shebang line from the first child to the root.'''
    
    p = root.firstChild()
    s = p and p.b or ''
    if not s.startswith('#!'):
        return
        
    lines = g.splitLines(s)
    nl = '\n\n' if root.b.strip() else ''
    root.b = lines[0] + nl + root.b
    p.b = lines[1:]
    p.setDirty()
    p.setMarked()
    root.setDirty()
    root.setMarked()
    c.setChanged()
    self.changed += 1
    g.trace('%s --> %s' % (p.h,root.h))
#@+node:ekr.20120602062004.12354: *5* move_doc_string
def move_doc_string(self,c,root):

    '''Move a leading docstring in the first child to the root node.'''
    
    p = root.firstChild()
    s = p and p.b or ''
    if not (s.startswith('"""') or s.startswith("'''")):
        return
        
    delim = '"""' if s.startswith('"""') else "'''"
    i = s.find(delim,3)
    if i == -1:
        return
        
    doc = s[:i+3]
    p.b = s[i+3:].lstrip()
    
    # Move docstring to front of root.b, but after any shebang line.
    nl = '\n\n' if root.b.strip() else ''
    if root.b.startswith('#!'):
        lines = g.splitLines(root.b)
        root.b = lines[0] + doc + nl + lines[1:]
    else:
        root.b = doc + nl + root.b
        
    p.setDirty()
    p.setMarked()
    root.setDirty()
    root.setMarked()
    c.setChanged()
    self.changed += 1
    g.trace('%s --> %s' % (p.h,root.h))
#@+node:ekr.20120602062004.12355: *5* rename_decls (test)
def rename_decls (self,c,root):
    
    '''Use a section reference for declarations.'''
    
    p = root.firstChild()
    h = p and p.h or ''
    
    tag = 'declarations'
    if not h.endswith(tag):
        return

    name = h[:-len(tag)].strip()
    decls = g.angleBrackets(tag)
    p.h = '%s (%s)' % (decls,name)
    
    i = root.b.find('@others')
    if i == -1:
        g.trace('can not happen')
        return
    else:
        nl = '' if i == 0 else '\n'
        root.b = root.b[:i] + nl + decls + '\n' + root.b[i:]

    p.setDirty()
    root.setDirty()
    root.setMarked()
    c.setChanged()
    self.changed += 1
    g.trace('%s --> %s' % (p.h,root.h))
#@+node:ekr.20120602062004.12356: *5* clean_blank_lines
def clean_blank_lines(self,c,p):
    
    '''Remove leading and trailing blank lines from all nodes.
    '''
    
    s = p.b
    if not s.strip():
        return
    
    result = g.splitLines(s)
    for i in 0,-1:
        while result:
            if result[i].strip():
                break
            else:
                del result[i]
        
    s = ''.join(result)
    if not s.endswith('\n'): s = s + '\n'
    if s != p.b:
        p.b = s
        p.setDirty()
        # p.setMarked()
        c.setChanged()
        self.changed += 1
        # if self.trace: g.trace(p.h)
        
#@+node:ekr.20120602062004.12357: *5* merge_comment_nodes
def merge_comment_nodes(self,c,p):
    
    '''Merge a node containing nothing but comments with the next node.'''

    h = p.h
    
    if p.hasChildren() or not h.strip().startswith('#'):
        return
        
    p2 = p.next()
    if p2:
        b = p.b.lstrip()
        b = b + ('\n' if b.endswith('\n') else '\n\n')
        p2.b = b + p2.b
        p.doDelete(p2)
        p2.setDirty()
        # p2.setMarked()
        c.setChanged()
        self.changed += 1
        if self.trace: g.trace(h,' --> ',p2.h)
#@+node:ekr.20120602062004.12358: *5* merge_extra_nodes
def merge_extra_nodes(self,c,p):
    
    '''Merge a node containing no class or def lines with the previous node'''
    
    s = p.b
    if p.hasChildren() or p.h.strip().startswith('<<') or not s.strip():
        return
        
    for s2 in g.splitLines(s):
        if s2.strip().startswith('class') or s2.strip().startswith('def'):
            return

    p2 = p.back()
    if p2:
        nl = '\n' if s.endswith('\n') else '\n\n'
        p2.b = p2.b + nl + s
        h = p.h
        p.doDelete(p2)
        p2.setDirty()
        # p2.setMarked()
        c.setChanged()
        self.changed += 1
        if self.trace: g.trace(h,' --> ',p2.h)
#@+node:ekr.20071113150213: *3* Clone all nodes to child node
# Clone all nodes specified in the child node called 'to be cloned'
root = p.copy()
child = p.firstChild()
assert(child.h.lower().strip() == 'to be cloned')
s = child.b
lines = g.splitLines(s)

for p in c.all_unique_positions():
    h = p.h.strip()
    for line in lines:
        if h == line.strip():
            print 'found',h
            clone = p.clone()
            clone.moveToLastChildOf(root)
c.redraw()
#@+node:ekr.20071113150213.1: *4* To be cloned
@test getHandlersForTag
@test unit testing with embedded class
@test move-lines-up
@test helpForMinibuffer
@test g.pdb
@test g.es_exception
@test g.es_trace
@test pasteText
@test g.getScript strips crlf
@test forward-end-word (start of word)
@test moveToHelper
@test scrollHelper
@test c.contractAllHeadlines
@test g.create_temp_file
@test fc.deleteFileWithMessage
@test compareHelper
@test apropos_bindings
@test apropos_find_commands
@test atFile_rename
@test g.removeExtraLws
@test moveUpOrDownHelper
@test compareHelper-warning
@test directiveKind4
@test isPlainKey
@test g.convertPythonIndexToRowCol
@test setMoveCol
@test leoBody.getSelectionAreas & test
@test parseLeoSentinel
@test selfInsertCommand-2 (replacing tabs)
@test extendHelper
@test onClick
@test selfInsertCommand-1
@test g.convertRowColToPythonIndex
@test round trip toUnicode toEncodedString
@test that all @test nodes in derived files start with if g.unitTesting
@test g.reportBadChars
#@+node:ekr.20141105055521.13: *3* Dictionary to Leo outline
'''
Transform a dictionary into an outline, so you can navigate through it.
'''

def dictionary_to_outline(p,dictionary):
    '''Transform a dictionary into an outline as p's children.'''
    # g.app.gui.frameFactory.masterFrame.currentWidget().leo_c
    sorted_keys = sorted([key for key in dictionary])
    for key in sorted_keys:
        new_node = p.insertAsLastChild().copy()
        new_node.h = key
        if "dict" in str(type(dictionary[key])):
            self.dictionary_to_outline(new_node,dictionary[key])
        else:
            new_node.b = str(dictionary[key])
#@+node:ekr.20071113191025: *3* Remove all @test nodes
count = 0
p = c.rootVnode()
while p:
    h = p.h
    if h.startswith('@test '):
        next = p.nodeAfterTree()
        print 'deleting',h
        count += 1
        p.doDelete()
        p = next
    else:
        if h.startswith('@thin'): print h
        p.moveToThreadNext()
c.redraw()

print '%d @test nodes deleted' % count
#@+node:ekr.20150416064259.1: ** Comparing files & nodes
#@+node:EKR.20040424065452: *3* Compare files
p1 = r"c:\prog\leoMenu(1).py"
p2 = r"c:\prog\leoMenu(2).py"

f1 = open(p1,"rb") ; f2 = open(p2,"rb")
lines1 = f1.readlines()
lines2 = f2.readlines()
f1.close() ; f2.close()

f1 = open(p1,"rb") ; f2 = open(p2,"rb")
text1 = f1.read()
text2 = f2.read()
f1.close() ; f2.close()

cr1 = text1.count('\r')
cr2 = text2.count('\r')

print '-'*20
print "lines ",len(lines1),len(lines2)
print "chars ",len(text1),len(text2)
print "non-cr",len(text1)-cr1,len(text2)-cr2
print "cr    ",cr1,cr2
#@+node:EKR.20040424091411: *3* Compare ignoring newlines
p1 = r"c:\prog\leoMenu(1).py"
p2 = r"c:\prog\leoMenu(2).py"

f1 = open(p1,"rb") ; f2 = open(p2,"rb")
g.es("equal (raw mode)",f1.read()==f2.read())
f1.close() ; f2.close()

f1 = open(p1) ; f2 = open(p2)
g.es("equal (text mode)",f1.read()==f2.read())
f1.close() ; f2.close()
#@+node:ekr.20111017085134.16202: *3* Compare old and new nodes
old,new = None,None
for child in p.children():
    if child.h.startswith('old'):
        old = child.copy()
    if child.h.startswith('new'):
        new = child.copy()
if old and new:
    print(old.b == new.b)
    if old.b != new.b:
        print('*' * 20)
        print(len(old.b),len(new.b))
        old_lines = g.splitLines(old.b)
        new_lines = g.splitLines(new.b)
        for i in range(max(len(old_lines),len(new_lines))):
            if (i < len(old_lines) and i < len(new_lines) and
                old_lines[i] == new_lines[i]
            ):
                pass
            else:
                if i < len(old_lines):
                    print('old %2d %s' % (i,repr(old_lines[i])))
                if i < len(new_lines):
                    print('new %2d %s' % (i,repr(new_lines[i])))
#@+node:ekr.20141105055521.18: *3* Tool for diffing Leo files
'''
From: Terry Brown

The script below is a tool for diffing two Leo files. The attached
screenshot illustrates the output for two different revisions of
LeoPyRef.leo.

``- nodename``
    indicates a node which disappeared
``+ nodename``
    a node which is new,
``!v nodename`` followed by ``!^ nodename``
    a node with an unchanged heading but changed content, the first
    linking to the old version, the second linking to the new version

If you have the bookmarks.py plugin active, you can double click nodes
to jump to the original(s).

'''
from leo.core.leoNodes import vnode
if not hasattr(vnode, 'insertAsLastChild'):
    # add insertAsLastChild method to vnodes
    def ialc(self):
        vnode(self.context)._linkAsNthChild(self, len(self.children))
        return self.children[-1]
    vnode.insertAsLastChild = ialc

from_filename = g.app.gui.runOpenFileDialog('From (old) file', [('Leo', '*.leo')])
to_filename = g.app.gui.runOpenFileDialog('To (new) file', [('Leo', '*.leo')])

# from_filename = "/mnt/shuttle/bkup/usr1/2012-07-13/home/tbrown/.leo/.todo.leo"
# to_filename = "/mnt/shuttle/bkup/usr1/2012-07-15/home/tbrown/.leo/.todo.leo"

from_c = g.openWithFileName(from_filename, c)
to_c = g.openWithFileName(to_filename, c)

vf = from_c.hiddenRootNode
vt = to_c.hiddenRootNode

assert from_c != c
assert to_c != c
assert from_c != to_c

nd = c.rootPosition().insertAfter()
nd.copy().back().moveAfter(nd)
nd.h = 'diff @bookmarks'

def text_match(a, b):
    return (a.h == b.h, 
            a.h == b.h and a.b == b.b)
def gnx_match(a, b):
    return (a.h == b.h and a.gnx == b.gnx, 
            a.h == b.h and a.b == b.b and a.gnx == b.gnx)

def diff_trees(vf, vt, path):

    fonly = []  # nodes only in from tree
    tonly = []  # nodes only in to tree
    diffs = []  # nodes which occur in both but have different descendants

    # count number of times each headline occurs as a child of
    # each node being compared
    count_f = {}
    for cf in vf.children:
        count_f[cf.h] = count_f.get(cf.h, 0) + 1
    count_t = {}
    for ct in vt.children:
        count_t[ct.h] = count_t.get(ct.h, 0) + 1

    for cf in vf.children:
        
        for ct in vt.children:
            
            if count_f[cf.h] == 1 and count_t[ct.h] == 1:
                equal = text_match
            else:
                equal = gnx_match
            
            head_eq, body_eq = equal(cf, ct)
            
            if body_eq:
                diffs.append(diff_trees(cf, ct, path+[vf.h]))
                
                break
            elif head_eq:
                d = diff_trees(cf, ct, path+[vf.h])
                if d:
                    d.h = '!v '+d.h
                else:
                    d = vnode(nd.v.context)
                    d.h = '!v '+cf.h
                d.b = "file://%s/#%s\\n\\n%s" % (
                    from_filename, 
                    '-->'.join((path+[vf.h]+[cf.h])[1:]),
                    cf.b
                )
                diffs.append(d)
                d = vnode(nd.v.context)
                d.h = '!^ '+cf.h
                d.b = "file://%s/#%s\\n\\n%s" % (
                    to_filename, 
                    '-->'.join((path+[vt.h]+[ct.h])[1:]),
                    ct.b
                )
                diffs.append(d)
                break
        else:
            fonly.append(cf)
            
    for ct in vt.children:
        
        for cf in vf.children:
            
            if count_f[cf.h] == 1 and count_t[ct.h] == 1:
                equal = text_match
            else:
                equal = gnx_match
            
            head_eq, body_eq = equal(cf, ct)
            if head_eq or body_eq:
                # no need to recurse matches again
                break

        else:
            tonly.append(ct)

    if not any(diffs) and not fonly and not tonly:
        return None
        
    vd = vnode(nd.v.context)
    vd.h = vf.h
    for d in diffs:
        if d:
            vd.children.append(d)
    for f in fonly:
        n = vd.insertAsLastChild()
        n.h = '- '+f.h
        n.b = "file://%s/#%s" % (from_filename, '-->'.join((path+[vf.h]+[f.h])[1:]))
    for t in tonly:
        n = vd.insertAsLastChild()
        n.h = '+ '+t.h
        n.b = "file://%s/#%s" % (to_filename, '-->'.join((path+[vf.h]+[t.h])[1:]))
        
    return vd

v = diff_trees(vf, vt, [])
if v:
    nd.v.children.extend(v.children)  # snip off <hidden root node>

c.bringToFront()
c.redraw()
#@+node:ekr.20150416063838.1: ** Data
#@+node:ekr.20050310082013: *3* AutoIt script from e
@ http://sourceforge.net/forum/message.php?msg_id=3039793

heres a short script to open a leo, then a New leo from that one, pause then exit.

still have to work out how to collect error output, especially if Leo doesn't start at all
can't then depend on Leo error reporting!

make a node
@url ./leoopen1.au3

make another
@nosent leoopen1.au3
@c

@language elisp

; AutoIt Version: 3.0 a BASIC like language
; http://www.hiddensoft.com

; Opens Leo with no filename, then opens a new, closes it
; Preliminary, will eventually programatically create
; leoPlugins.txt and various leoSettings.leo
; and run commands in all permutations looking for failures.

; Paths are hardwired but later scripts will be created on the fly
; maybe Leo can have a -trace mode to output to file a log of activities?

; exit when CTRL+ALT+x is pressed
HotKeySet("^!x", "MyExit")

Func MyExit()
    Exit 
EndFunc 

Opt("SendKeyDelay", 1)
Opt("WinWaitDelay", 80)

;fix path to leo.py
Run("python c:\c\leo\V43leos\leo\src\leo.py")
Sleep(2700) 

WinWaitActive("untitled")   

Sleep(700) 
Send("!Fn")  ; how to tell if there are errors?
Sleep(2700) 

WinWaitActive("untitled1")
Send("!Fx")
Sleep(2700) 

;careful you don't close the leo you are working from
Send("!Fx")
#@+node:ekr.20061220094557: *3* Backup Outlook Express files
# http://support.microsoft.com/default.aspx/kb/270670
# See Notes child for the manual instructions taken from the URL above.

import glob
import os

theDir = (r'C:\Documents and Settings\Ed\Local Settings\Application Data\Identities' +
    r'\{B14A7A05-7759-4249-A9C2-FDC8B4FDC50C}\Microsoft\Outlook Express')

files = glob.glob(theDir + r'\*.*')
files.sort()

#theFiles = [g.os_path_basename(z) for z in files]
#theFiles.sort()
#print g.listToString(theFiles)

dest = r'c:\OutlookBackup'
if not g.os_path_exists(dest):
    g.es_print('***** Creating %s' % dest)
    os.mkdir(dest)

for inputName in files:
    base = g.os_path_basename(inputName)
    outputName = g.os_path_join(dest,base)
    inputFile = file(inputName)
    s = inputFile.read()
    inputFile.close()
    outputFile = file(outputName,'w')
    outputFile.write(s)
    outputFile.close()
    g.es_print('***** Wrote %s' % outputName)
#@+node:ekr.20061220094557.1: *4* Notes
@killcolor
@

To backup Outlook Express data:
• Copy mail files to a backup folder 
• Export the Address Book to a file 
• Export the mail account to a file 
• Export the news account to a file

To restore or import Outlook Express data:
• Import messages from the backup folder 
• Import the Address Book file 
• Import the mail account file 
• Import the news account file 

Step 1: Copy Mail Files to a Backup Folder (Make a backup copy of your Outlook Express e-mail message files):

(EKR: get the file name)

1. On the Tools menu, click Options. 
2. On the Maintenance tab, click Store Folder. 
3. Select the folder location, and then press CTRL+C to copy the location.
4. Click Cancel, and then click Cancel again to close the dialog box. 

r'C:\Documents and Settings\Ed\Local Settings\Application Data\Identities\{B14A7A05-7759-4249-A9C2-FDC8B4FDC50C}\Microsoft\Outlook Express'

(EKR: open the directory)

5. Click Start, and then click Run. 
6. In the Open box, press CTRL+V, and then click OK.

(EKR: Copy all files to a folder)

7. On the Edit menu, click Select All. 
8. On the Edit menu, click Copy, and then close the window.
9. Right-click any empty space on your desktop, click New, and then click Folder. 
10. Type mail backup for the folder name, and then press ENTER. 
11. Double-click the Mail Backup folder to open it. 
12. On the Edit menu, click Paste. 
13. Close the Mail Backup window. 

Step 2: Export the Address Book to a File

A .WAB (Windows Address Book) file is used by Outlook Express 5.x and 6.0 versions, even if multiple Identities are used. The individual data for each Identity is stored in a folder, by user name, within the .WAB file in use.

Exporting this data, while logged in to a specific Identity, is the only means of segregating the Address Book data. If the .WAB file becomes dissociated from the user Identities, the data can only be exported in total - not folder by folder.

Another reason to export the .WAB file to a .csv file is that if the .WAB file is shared with Microsoft Outlook, the addresses are stored in the *.pst file in Outlook. When you export the file from the Outlook Express File menu to a *.csv file it exports the correct contacts. If the Address Book is shared with Microsoft Outlook, you are not able to export from within the Address Book on the File menu. This option is dimmed or not available.

To export your Outlook Express address book: 1. On the File menu, click Export, and then click Address Book. 
2. Click Text File (Comma Separated Values), and then click Export. 
3. Click Browse. 
4. Locate the Mail Backup folder that you created. 
5. In the File Name box, type address book backup, and then click Save. 
6. Click Next. 
7. Click to select the check boxes for the fields that you want to export, and then click Finish. 
8. Click OK and then click Close. 

Step 3: Make a backup copy of your Outlook Express mail account:

1. On the Tools menu, click Accounts. 
2. On the Mail tab, click the mail account that you want to export, and then click Export. 
3. In the Save In box, locate the Mail Backup folder on your desktop, and then click Save. 
4. Repeat these steps for each mail account that you want to export. 
5. Click Close. 

Step 4: Make a backup copy of your Outlook Express news accounts:

1. On the Tools menu, click Accounts. 
2. On the News tab, click the news account that you want to export, and then click Export. 
3. In the Save In box, use locate the Mail Backup folder on your desktop, and then click Save. 
4. Repeat these steps for each news account that you want to export. 
5. Click Close. 

Importing Outlook Express data

To restore data, you may need to re-create the Identities for each user, prior to using the following steps.
***Repeat each step, as needed, for each Identity.

Step 1: Import Messages from the Backup Folder (Import your Outlook Express e-mail messages from the Backup folder)

1. On the File menu, point to Import, and then click Messages. 
2. In the Select an e-mail program to import from box, click Microsoft Outlook Express 5 or Microsoft Outlook Express 6, and then click Next. 
3. Click Import mail from an OE5 store directory or Import mail from an OE6 store directory, and then click OK. 
4. Click Browse, and then click on the Mail Backup folder on your desktop. 
5. Click OK, and then click Next. 
6. Click All folders, click Next, and then click Finish. 

Step 2: Import the Address Book File

1. On the File menu, click Import, and then click Other Address Book. 
2. Click Text File (Comma Separated Values), and then click Import. 
3. Click Browse. 
4. Locate the Mail Backup folder on your desktop, click the address book Backup.csv file, and then click Open. 
5. Click Next, and then click Finish. 
6. Click OK, and then click Close. 

Step 3: Import the Mail Account File

1. On the Tools menu, click Accounts. 
2. On the Mail tab, click Import. 
3. In the Look In box, locate the Mail Backup folder on your desktop. 
4. Click the mail account that you want to import, and then click Open. 
5. Repeat these steps for each mail account that you want to import. 
6. Click Close. 

Step 4: Import the Newsgroup Account File

1. On the Tools menu, click Accounts. 
2. On the News tab, click Import. 
3. In the Look In box, locate the Mail Backup folder on your desktop. 
4. Click the news account that you want to import, and then click Open. 
5. Repeat these steps for each news account that you want to import. 
6. Click Close.

@color
#@+node:ekr.20051110110853: *3* Clear all uA's, tnodeLists, etc.
# Use these with caution.
#@+node:ekr.20040312021734.1: *4* Clean unused tnodeLists
count = 0
for p in c.all_unique_positions():
    count += 1
    # Empty tnodeLists are not errors because they never get written to the .leo file.
    v = p.v
    if hasattr(v,"tnodeList") and len(v.tnodeList) > 0 and not v.isAnyAtFileNode():
        g.es("deleting tnodeList for " + `v`,color="blue")
        delattr(v,"tnodeList")
        c.setChanged()

s = "%d nodes" % count
print s ; g.es(s)
#@+node:ekr.20051110105027.102: *4* Clear all timestamps
# About the only time you should run this script is when:
# - changing the format of timestamps in nodeIndices.setTimestamp or
# - when making a retroactive change to leoID.txt.

if 0: # This is usually a very bad idea.

    for p in c.all_positions():
        p.v.fileIndex = None

    g.es("all timestamps cleared")
#@+node:ekr.20040318091620: *4* Clear all uAs (unknown attributes)
put = g.es_print
for p in c.all_positions():
    if p.v.u:
        put("deleting v.u:",p.h,
            g.listToString(p.v.u.keys()))
        p.v.u = None

put('done') 
c.redraw()
#@+node:ekr.20050219054039: *3* Convert ChoiceMail .ini file to .csv file
@ These scripts work on the child nodes as shown.

The address children contained the actual data (removed for privacy)
#@+node:ekr.20050218174326.1: *4* Scipt to remove rejected entries
p = p.firstChild().firstChild()
s = p.b
lines = g.splitLines(s)
reject = 'reason=Rejected before registration'
result = []
entries = []
for line in lines:
    if line.startswith('['):
        # Add all previous entries
        for entry in entries:
            result.append(entry)
        entries = []
        entries.append(line)
    elif line.startswith(reject):
        # Kill all previous entries.
        entries = []
    else:
        entries.append(line)

result = ''.join(result)
c.setBodyString(p,result)
print 'done!'
#@+node:ekr.20050218170806.1: *5* @killcolor
@killcolor
#@+node:ekr.20050218170806.2: *6* address
#@+node:ekr.20050218170806: *4* Script to clean address
p = p.firstChild().firstChild()
s = p.b
lines = g.splitLines(s)
prefixes = (
    'access=',
    'bccsender=',
    'filtered=',
    'registered=',
    'messagetocount=',
    'messagecount=',
    'lastmessagedate=',
    'lastsource=',
    'replyToAddresses=',
    'creationdate=',
    'reason=Approved before registration',
    'reason=Address Book Contact',
    'registrationDate=',
    'registrationRequestSent=',
    'reason=Pre-approved sender',
    'preaccepted=1',
)
result = []
for line in lines:
    for prefix in prefixes:
        if line.startswith(prefix):
            # print 'removing',line
            break
    else:
        if line.startswith('name='):
            result.append(line[5:])
        else:
            result.append(line)

result = ''.join(result)
c.setBodyString(p,result)
print 'done!'
#@+node:ekr.20050219054351: *5* @killcolor
@killcolor
#@+node:ekr.20050219054351.1: *6* address
#@+node:ekr.20050218184044.10: *4* Script to create comma delimited lists
address_p = p.firstChild()
result_p = p.firstChild().next()

result = []
entries = []
for child in address_p.children():
    s = child.b
    lines = g.splitLines(s)
    for line in lines:
        if line.startswith('['):
            # Add all previous entries
            if entries:
                if len(entries) > 1:
                    # entries2 = [entries[0],entries[1]]
                    # entries2.extend(entries[2:])
                    result.append(','.join(entries[:2]))
                else:
                    result.append(entries[0])
            entries = [] ; entries2 = []
            entries.append(line.rstrip()[1:-1])
        elif line.strip():
            entries.append(line.rstrip())

result.sort()
result = '\n'.join(result)
c.setBodyString(result_p,result)
print 'done!'
#@+node:ekr.20050219054039.1: *5* address
#@+node:ekr.20050218184044.11: *5* result
#@+node:ekr.20130526065545.18367: *3* Import nodes from an email account
# https://groups.google.com/forum/?fromgroups#!searchin/leo-editor/imaplib/leo-editor/i_U-PBv0Ek0/c0XsmlI_UugJ

import imaplib

def munge(part):
    s = g.toUnicode(repr(part[0][1]))
    s = s.strip("'")
    return s.replace('\\r\\n','\n')

m = imaplib.IMAP4_SSL('imap.gmail.com',993) # Require SSL: Yes
m.login(<< your login name>>,<< your password>>)
print('connected to edreamleo@gmail.com')
# for z in m.list():
    # print(z)
try:
    box = 'Leo/Later' # 'INBOX'
    m.select(box,True) # readonly.
    ok, aList = m.search(None,'ALL')
    if ok == 'OK':
        parent = p.insertAsLastChild()
        parent.h = box
        aList = [g.toUnicode(z) for z in aList]
        nums = aList[0].split()
        for num in nums:
            # ok,data = m.fetch(num,'(RFC822)') # Gets everything.
            ok,part = m.fetch(num,"(UID BODY[TEXT])")
            if ok != 'OK': continue
            print('reading %s %s' % (box,num))
            child = parent.insertAsLastChild()
            body = munge(part)
            ok,part = m.fetch(num,"(UID BODY[HEADER.FIELDS (FROM)])")
            if ok == 'OK': body = munge(part) + body
            child.b = '@nocolor\n\n' + body
            ok,part = m.fetch(num,"(UID BODY[HEADER.FIELDS (SUBJECT)])")
            if ok == 'OK':
                head = munge(part)
                tag = 'Subject: '
                if head.startswith(tag): head = head[len(tag):]
                child.h = head.replace('\n','')
            else:
                child.h = '%s %s' % (box,num)
finally:
    m.close()
    m.logout()
p.contract()
c.redraw()
print('done')
#@+node:ekr.20061018084920: *3* Munge database records
'''A script to munge database records so they conform to an existing db's format.'''
# Almost infinitely easier to do this in Python rather than FileMaker Pro's laughable scripting language.
path = r'c:\rebecca\REBECCA9_97.txt'
path2 = r'c:\rebecca\REBECCA9_97-converted.txt'
f = file(path)
s = f.read() ; f.close()
f = file(path2,'w')
print '-' * 40, len(s)
lines = s.split('\n')
n = 0
for line in lines:
    n += 1
    if not line.strip(): continue
    # bug: fails for fields containing commas.  Should use a regex instead.
    fields = line.split(',')
    result = []
    info1,last,full,addr1,addr2,zip,info2,info3 = fields
    assert full.startswith('"')
    data = full[1:-1].strip().split(' ')
    first = '"%s"' % data[0].strip()
    data = addr2[1:-1].strip().split(' ')
    city = '"%s"' % ''.join(data[:-2])
    state = '"%s"' % data[-2].upper()
    for field in (first,last,addr1,city,state,zip,info1,info2,info3):
        if field.startswith('"'):
            # Capitalize each word.
            s = field[1:-1].strip()
            aList = s.split(' ')
            s = ' '.join([z.capitalize() for z in aList])
            result.append('"%s"' % s)
        else:
            result.append(field)
    s = ','.join(result)
    print s
    f.write(s+'\n')
f.close()
print '%d records' % n
#@+node:ekr.20050427101736: *3* Prototype: Zip files experiments
class ZipFile(  	file[, mode[, compression]])
    Open a ZIP file, where file can be either a path to a file (a string) or a file-like object. The mode parameter should be 'r' to read an existing file, 'w' to truncate and write a new file, or 'a' to append to an existing file. For mode is 'a' and file refers to an existing ZIP file, then additional files are added to it. If file does not refer to a ZIP file, then a new ZIP archive is appended to the file. This is meant for adding a ZIP archive to another file, such as python.exe. Using

cat myzip.zip >> python.exe

    also works, and at least WinZip can read such files. compression is the ZIP compression method to use when writing the archive, and should be ZIP_STORED or ZIP_DEFLATED; unrecognized values will cause RuntimeError to be raised. If ZIP_DEFLATED is specified but the zlib module is not available, RuntimeError is also raised. The default is ZIP_STORED. 
#@+node:ekr.20050427102426: *4* @url http://docs.python.org/lib/module-zipfile.html
#@+node:ekr.20050427101736.1: *4* write zip
import zipfile

path = r'c:\prog\leoCVS\leo\test\ziptest.zip'

zf = zipfile.ZipFile(path,'w',zipfile.ZIP_DEFLATED)
zf.writestr('This is a test: EKR','ziptest')
zf.close()
#@+node:ekr.20050427101736.2: *4* read zip
import zipfile

path = r'c:\prog\leoCVS\leo\src\leoPy.leo'
path = r'c:\prog\leoCVS\leo\test\ziptest.zip'

if zipfile.is_zipfile(path):
    try:
        zf = zipfile.ZipFile(path)
        names = zf.namelist()
        for name in names:
            print 'name',repr(name),'contents',zf.read(name)
        zf.close()
    except Exception:
        g.es_exception()
        zf = None
#@+node:ekr.20060928193919: *3* Prototype: zodb get/put scripts
#@+node:ekr.20150416171257.1: *4* @ignore zodb buttons
#@+node:ekr.20060928193919.1: *5* @button zodb-get-tree
name = c.config.getString('zodb_storage_file')
db = g.init_zodb(name)
connection = db.open()
try:
    fileName = c.fileName()
    root = connection.root()
    v = root.get(fileName)
    if v:
        g.es_print('get-tree: %s' % repr(v))
    else:
        g.es_print('get-tree failed: %s' % fileName)
finally:
    get_transaction().commit()
    connection.close()
#@+node:ekr.20060928193919.2: *5* @button zodb-get-node
name = c.config.getString('zodb_storage_file')
db = g.init_zodb(name)
connection = db.open()
try:
    fileName = c.fileName()
    root = connection.root()
    key = 'vnode: %s' % p.h
    v = root.get(key)
    if v:
        g.es_print('get-node %s: %s' % (key,repr(v)))
    else:
        g.es_print('get-node %s failed.' % key)
finally:
    get_transaction().commit()
    connection.close()
#@+node:ekr.20060928193919.3: *5* @button zodb-put-tree
name = c.config.getString('zodb_storage_file')
db = g.init_zodb(name)
connection = db.open()
try:
    fileName = c.fileName()
    root = connection.root()
    root[fileName] = c.rootPosition().v
finally:
    get_transaction().commit()
    connection.close()
#@+node:ekr.20060928193919.4: *5* @button zodb-put-node
name = c.config.getString('zodb_storage_file')
db = g.init_zodb(name)
connection = db.open()
try:
    fileName = c.fileName()
    root = connection.root()
    v = p.v.detach()
    key = 'vnode: %s' % v.h
    g.es_print('put-node %s :%s' % (v.h,repr(v)))
    root[key] = v
finally:
    get_transaction().commit()
    connection.close()
#@+node:ekr.20050824190822: *3* Prototype: Zope test
@color

import ZODB
import ZODB.FileStorage

storage = ZODB.FileStorage.FileStorage(r"c:\prog\zopeTemp\leo.fs")
# g.trace(storage)
db = ZODB.DB(storage)
try:
    try:
        print('-' * 20)
        print('opening ZODB')
        connection = db.open()
        g.trace(connection)
        root = connection.root()
        if 1:
            t = get_transaction()
            t.begin()
            # root.clear()
            root ['count'] = root.get('count',0) + 1
            t.commit()
        print(root)
        connection.close()
    except Exception:
        g.es_exception()
finally:
    print('closing ZODB')
    db.close()
#@+node:ekr.20181019042505.1: ** Developing & maintaining Leo
#@+node:ekr.20181019042544.1: *3* script: regularize settings names
# Replace underscore by - in settings names.
# See https://github.com/leo-editor/leo-editor/issues/993
g.cls()
import re
kinds = (
    'Bool', 'Color', 'Data',
    'Directory', 'Encoding', # Not found.
    'Float', 'Int',
    'Path', 'Ratio', # Not found.
    'String',
)
for kind in kinds:
    print('===== %s =====\n' % kind)
    pat = re.compile(r"get%s\s*\([\'\"]([\w_]+)[\'\"][,)]" % kind)
    for p in c.all_positions():
        s = p.b
        i, found = 0, False
        while True:
            m = pat.search(s[i:])
            if not m:
                break
            start, end = m.start(1), m.end(1)
            all = s[i+m.start(0):i+m.end(0)]
            word = s[i+start:i+end]
            if '_' in word:
                found = True
                print('')
                print('-----', p.h)
                print(all)
                s = s[:i+start] + word.replace('_','-') + s[i+end:]
                print(s[i+m.start(0):i+m.end(0)])
            i += end
        if found:
            assert p.b != s, p.h
            # print(s)
            p.b = s
            p.v.setDirty()
c.setChanged()
c.redraw()     
#@+node:ekr.20181019043251.1: *3* @ignore at-button nodes in leoPy.leo
#@+node:ekr.20181019043237.1: *4* @button backup-repo
@language python
import os
import subprocess
import sys
import time
win = sys.platform.startswith('win')
old_dir = g.os_path_abspath('.')
if win:
    new_dir = r'C:\leo.repo\leo-editor'
    path = r'C:\Users\edreamleo\Backup'
else:
    new_dir = '/home/edward/leo.repo/leo-editor'
    path = '/home/edward/Backup'
assert g.os_path_exists(new_dir), repr(new_dir)
assert g.os_path_exists(path), repr(path)
stamp = time.strftime("%Y%m%d-%H%M%S")
fn = g.os_path_finalize_join(path, 'leo-bundle-all-%s' % (stamp))
bundle_command = 'git bundle create %s --all' % fn
print(bundle_command)
os.chdir(new_dir)
# os.system(bundle_command)
proc = subprocess.Popen(bundle_command, shell=True)
proc.wait()
os.chdir(old_dir)
print('done! wrote %s' % fn)
#@+node:ekr.20181019042717.1: *3* @ignore at-button nodes in LeoSettings.leo
#@+node:ekr.20181019042907.1: *4* @button check-bindings
'''Check for commands that exist in some but not all key- binding sets.'''
g.cls()
import leo.core.leoConfig as leoConfig
# import leo.core.leoTest as leoTest
parser = leoConfig.SettingsTreeParser(c)
# Add these as required to handle commands defined by plugins.
optionalCommandPrefixes = ['group-operations']
optionalCommandNames = [
    # These are the command names as defined by plugins.
    # LeoSlideShows.leo defines buttons whose commands do not end in '-command'.
    'next-slide-command','next-slide-show-command',
    'prev-slide-command','prev-slide-show-command',
    # The ipython plugin.
    'start-ipython','get-ipython-results','push-to-ipython',
    # The viewrendered plugin.
    'vr-toggle',
]
setNames = []
setsDict = {} # keys are set names, values are dicts of command names.
shortcutsDict = {}
@others
main()
#@+node:ekr.20181019042907.2: *5* defineSetNames
def defineSetNames():
    global setNames
    if 1:
        setNames = []
        for p in c.allNodes_iter():
            h = p.headString()
            if h.startswith('@keys'):
                h = h[5:].strip()
                if h not in setNames:
                    setNames.append(h)
        g.es('Found these sets...')
        for setName in setNames:
            g.es_print('  %s' % str(setName))
    else:
        setNames = [
            'Default Emacs shortcuts',
            'Legacy Leo bindings',
            'Legacy Leo shortcuts with important Emacs bindings',
            'No bindings',
            'EKR bindings: a mix',
            'EKR bindings: Mode-oriented',
        ]

    setNames.sort()
#@+node:ekr.20181019042907.3: *5* doSet
def doSet(p,name):

    global shortcutsDict

    shortcutsDict = {}

    for p in p.subtree_iter():
        if p.headString().startswith('@shortcuts'):
            doShortcuts(p,name)
#@+node:ekr.20181019042907.4: *5* doShortcuts
def doShortcuts(p,setName):

    global parser,setsDict,shortcutsDict

    d = setsDict.get(setName,{})
    s = p.bodyString()
    lines = g.splitLines(s)
    for line in lines:
        line = line.strip()
        if line and not line.startswith('#'):
            commandName,si = parser.parseShortcutLine('test',line)
            if not si:
                g.es_print('In %s:\nmissing "=" in shortcut line:%s' % (
                    p.headString(),repr(line)),color='red')
            else:
                # Having multiple shortcuts for a command if fine,
                # But having multiple commands for a shortcut is not.
                shortcut = si.stroke
                pane = si.pane
                if shortcut not in (None,'None','none'):
                    aList = shortcutsDict.get(shortcut,[])
                    if aList:
                        for commandName2,pane2 in aList:
                            if pane == pane2:
                                g.es_print('duplicate shortcut %s in set %s: previous command: %s' % (
                                    shortcut,setName,commandName2),color='red')
                        else:
                            aList.append((commandName,pane),)
                    else:
                        shortcutsDict [shortcut] = [(commandName,pane),]

                data = d.get(commandName)
                if data:
                    shortcut2,pane2 = data
                    if shortcut == shortcut2 and pane == pane2:
                        g.es_print('duplicate %s in set %s' % (
                            commandName,setName),color='red')
                else:
                    data = shortcut,pane
                    d[commandName] = data

    setsDict[setName] = d
#@+node:ekr.20181019042907.5: *5* checkSets
def checkSets():

    global setNames, optionalCommandPrefixes, optionalCommandNames
    # Compute the union of all the command names.
    allNames = {}
    for setName in setNames:
        d = setsDict.get(setName)
        if d:
            for key in d.keys():
                allNames[key] = key
        else:
            g.es_print('No setsDict for %s' % (repr(setName)),color='red')
    keys = sorted(allNames.keys())
    # Warn about missing names.
    for setName in ('No bindings',): # setNames:
        d = setsDict.get(setName)
        if d:
            for key in keys:
                if key not in ('none','None',None) and key not in d.keys():
                    # Don't warn about missing 'enter-xxx-mode' commands.
                    if (
                        not (key.startswith('enter-') and key.endswith('-mode')) and
                        not (key.startswith('press-') and key.endswith('-button'))
                    ):
                        g.es_print('%s is missing %-35s = None' % (setName,repr(key)))
        else:
            g.es_print("'@keys No bindings' not found",color='blue')
    # Warn about undefined commands.
    for key in keys:
        if not c.commandsDict.get(key):
            ok = False
            # full-command and quick command are weird special cases.
            if key not in ('None',None,'full-command','quick-command'):
                # Don't warn about missing 'enter-xxx-mode' commands.
                if key.startswith('enter-') and key.endswith('-mode'):
                    ok = True
                elif key.startswith('press-') and key.endswith('-button'):
                    ok = True
                for prefix in optionalCommandPrefixes:
                    if key.startswith(prefix):
                        ok = True
                for optionalCommand in optionalCommandNames:
                    if key == optionalCommand:
                        ok = True
                if not ok:
                    g.es_print('Undefined command name: %s' % (key))
#@+node:ekr.20181019042907.6: *5* main
def main ():

    global setNames
    defineSetNames()

    g.es_print('-' * 40)
    seen = {}
    for p in c.allNodes_iter():
        h = p.headString()
        if h.startswith('@keys'):
            h = h[5:].strip()
            if not seen.get(h):
                seen[h] = True
                doSet(p,h)
    checkSets()
    g.es('Check Bindings done')
#@+node:ekr.20181019042834.1: *4* @button check-menus-cmds
'''Check that all commands mentioned in the @menus tree actually exist.'''

def checkMenu (p):
    for p2 in p.subtree_iter():
        if p2.h.startswith('@item'):
            checkItem(p.h,p2)

def checkItem (menuName,p):
    h = p.h[len('@item'):].replace('&','').replace('*','').strip()
    if h != '-' and h not in c.commandsDict:
        print ('command not found: %s: %s' % (menuName,p.h))

menus = g.findNodeAnywhere(c,'@menus')
assert menus, 'no @menus tree'
for p in menus.subtree_iter():
    if p.h.startswith('@menu'):
        checkMenu(p.copy())

print ('done')
#@+node:ekr.20181019042808.1: *4* @button check-settings
'''Check the consistency of all settings.'''
# https://github.com/leo-editor/leo-editor/issues/993

class Controller:
    @others

Controller(c).run()
#@+node:ekr.20181019042808.2: *5* ctor
def __init__(self, c):
    self.c = c
    self.errors = 0
    # Commanders...
    self.core = None # leoPy.leo.
    self.plugins = None # leoPlugins.leo.
    self.settings = None # leoSettings.leo.
#@+node:ekr.20181019042808.3: *5* check & helpers
def check(self, configs_d, settings_d):
    munge = self.munge
    table = ('Bool', 'Int', 'Float', 'Ratio', 'Path', 'String',) # 'Color', 'Font',
    #
    # Print missing user settings...
    for kind in table:
        config_key = 'get%s' % kind
        settings_key = '@%s' % kind.lower()
        configs = configs_d.get(config_key, [])
        settings = settings_d.get(settings_key, [])
        m_configs = [munge(z) for z in configs]
        m_settings = [munge(z) for z in settings]
        missing = set([z for z in m_configs if not z in m_settings])
        aList = [z for z in missing if self.filter_user_config(z)]
        if aList:
            print('\nmissing %s %s settings...\n' % (len(aList), settings_key))
            for z in sorted(aList):
                aList2 = [z2 for z2 in configs if munge(z2) == munge(z)]
                g.printObj(aList2)
    #
    # Print missing calls to c.config.getX...
    for kind in table:
        config_key = 'get%s' % kind
        settings_key = '@%s' % kind.lower()
        configs = configs_d.get(config_key, [])
        settings = settings_d.get(settings_key, [])
        m_configs = [munge(z) for z in configs]
        m_settings = [munge(z) for z in settings]
        missing = set([z for z in m_settings if not z in m_configs])
        aList = [z for z in missing if self.filter_get_x(z)]
        if aList:
            print('\nmissing %s config.%s calls...\n' % (len(aList), config_key))
            for z in sorted(aList):
                aList2 = [z2 for z2 in settings if munge(z2) == munge(z)]
                g.printObj(aList2)
#@+node:ekr.20181019042808.4: *6* filter_get_x
def filter_get_x(self, setting):
    '''
    Return False if we can safely ignore a missing call to config.get(setting).
    
    Everything here is a hack. Some are bigger than others.
    '''
    munge = self.munge
    #
    # These *ivars* are set by the GlobalConfigManager class.
    # There *should* be settings for all of these, despite missing config.get calls.
    table = (
        # encodingIvarsDict...
            "default_at_auto_file_encoding",
            "default_derived_file_encoding",
            "new_leo_file_encoding",
        # defaultsDict...
            "color_directives_in_plain_text",
            "output_doc_chunks",
            "page_width",
            "tab_width",
            "tangle_outputs_header",
            "target_language",
            "underline_undefined_section_names",
        # ivarsDict
            "at_root_bodies_start_in_doc_mode",
            "create_nonexistent_directories",
            "output_initial_comment",
            "output_newline",
            "page_width",
            "read_only",
            "redirect_execute_script_output_to_log_pane",
            "relative_path_base_directory",
            "remove_sentinels_extension",
            "save_clears_undo_buffer",
            "stylesheet",
            "tab_width",
            "target_language",
            "trailing_body_newlines",
            "use_plugins",
            "undo_granularity",
            "write_strips_blank_lines",
    )
    table = [munge(z) for z in table]
    if setting in table:
        return False
    #
    # unitTest.leo tests test-darwin-setting and test-win32-setting
    if setting in ('testdarwinsetting', 'testwin32setting'):
        return False
    #
    # Stylesheets use these settings.
    for pattern in (
        'bg', 'border', 'color', 'fg', 'font',
        'leogreen', 'leoyello',
        'margin', 'padding', 'relief',
        'solarized', 'split-bar', 'text-foreground', 'tree-image',
    ):
        if setting.find(munge(pattern)) > -1:
            return False
    #
    # These plugins/use settings in non-standard ways.
    if setting.startswith(
        ('bookmarks', 'datenodes', 'http', 'opml',
        'rst3', 'todo', 'vim', 'zodb'),
    ):
        return False
    #
    # Find settings are defined in non-standard ways.
    for pattern in (
        'batch', 'change-text', 'find-text', 'ignore-case',
        'mark-changes', 'mark-finds', 'node-only', 'pattern-match',
        'reverse', 'search-body', 'search-headline', 'suboutline-only',
        'whole-word', 'wrap',
    ):
        if setting == munge(pattern):
            return False
    #
    # Issue a warning.
    return True
#@+node:ekr.20181019042808.5: *6* filter_user_config
def filter_user_config(self, setting):
    '''
    Return False if we can safely ignore a setting that does not exist in leoSettings.leo.
    
    Everything here is a hack. Some are bigger than others.
    '''
    munge = self.munge
    #
    # unitTest.leo tests test-darwin-setting and test-win32-setting
    if setting in ('testdarwinsetting', 'testwin32setting'):
        return False
    #
    # The calls to config.get* are commented out in the code,
    # but get_configs isn't smart enough to know that.
    for ignore in (
        'auto-set-ignore-case',
        'find-def-creates-clones',
        'qt-rich-text-zoom-in',
        'theme-name',
        'pytest-path', # In a (disabled) @button node
        'leo-to-html-%s', # Loads multiple settings from an .ini file.
    ):
        if setting == munge(ignore):
            return False
    #
    # Stylesheets use these settings.
    # It would be a major project to discover what settings
    # are actually used in the present stylesheet.
    for pattern in (
        'bg', 'border', 'color', 'fg', 'font',
        'leogreen', 'leoyello',
        'margin', 'padding', 'relief',
        'solarized', 'split-bar', 'text-foreground', 'tree-image',
    ):
        if setting.find(munge(pattern)) > -1:
            return False
    #
    # These plugins use settings in non-standard ways.
    if setting.startswith(
        ('activepath', 'bookmarks', 'datenodes', 'http', 'opml',
        'rst3', 'todo', 'vim', 'zodb'),
    ):
        return False
    #
    # Find settings are defined in non-standard ways.
    for pattern in (
        'batch', 'change-text', 'find-text', 'ignore-case',
        'mark-changes', 'mark-finds', 'node-only', 'pattern-match',
        'reverse', 'search-body', 'search-headline', 'suboutline-only',
        'whole-word', 'wrap',
    ):
        if setting == munge(pattern):
            return False
    #
    # Tangle/untagle settings are deprecated and imo should not exist.
    for pattern in (
        'output-doc-flag', 'tangle-batch-flag',
        'untangle-batch-flag', 'use-header-flag',
    ):
        if setting == munge(pattern):
            return False
    #
    # Issue a warning.
    return True
#@+node:ekr.20181019042808.6: *5* error
def error(self, s):
    print(s)
    self.errors += 1
#@+node:ekr.20181019042808.7: *5* get_commanders
def get_commanders(self):
    '''Open files as needed and set the commander ivars.'''

    def open_commander(fn):
        c = g.openWithFileName(fn, old_c=self.c, gui=g.app.nullGui)
        if not c:
            self.error('not found: %s' % fn)
        return c

    join, loadDir = g.os_path_join, g.app.loadDir
    self.core = open_commander(join(loadDir, '..', 'core', 'leoPy.leo'))
        # Opening LeoPyRef.leo would be slower.
    self.plugins = open_commander(join(loadDir, '..', 'plugins', 'leoPlugins.leo'))
    self.settings = open_commander(join(loadDir, '..', 'config', 'leoSettings.leo'))
#@+node:ekr.20181019042808.8: *5* get_configs & helpers
def get_configs(self):
    '''
    Return a dict containing a representation of all calls to x.config.getX.
    '''
    d = {}
    for c in (self.core, self.plugins):
        print('scanning: %s' % c.shortFileName())
        self.get_configs_from_outline(c, d)
    return d
#@+node:ekr.20181019042808.9: *6* get_configs_from_outline & helper
def get_configs_from_outline(self, c, d):
    '''
    Scan the outline for all calls to x.config.getX and add items to d.
    '''
    for p in c.all_positions():
        self.scan_for_configs(p, d)
    return d
#@+node:ekr.20181019042808.10: *7* scan_for_configs
def scan_for_configs(self, p, d):
    '''
    Scan the body text of p, finding all calls to config.getX.
    
    This code does not know about `if 0`, but does know about comments.
    '''
    kinds = (
        'getBool', 'getColor', 'getInt', 'getFloat',
        # '@font', # special case.
        'getPath', 'getRatio', 'getString',
    )
    i, s = 0, p.b
    while i < len(s):
        progress = i
        ch = s[i]
        if (
            ch == '@' and
            (g.match(s, i, '@ ') or g.match(s, i, '@\n')) and
            (i == 0 or s[i - 1] == '\n')
        ):
            # Skip the @doc part.
            i = s.find('\n@c', i)
            if i == -1: break
        elif ch == '#':
            i = g.skip_to_end_of_line(s, i)
        elif ch in ('"', "'"):
            i = g.skip_python_string(s, i, verbose=False)
        elif ch == '_' or ch.isalpha():
            j = g.skip_id(s, i)
            kind = s[i: j]
            if kind in kinds:
                # We have found a call to getBool, etc.
                i = g.skip_ws(s, j)
                if g.match(s, i, '('):
                    i = g.skip_ws(s, i + 1)
                    if g.match(s, i, '"') or g.match(s, i, "'"):
                        j = g.skip_string(s, i)
                        name = s[i + 1: j - 1]
                        aList = d.get(kind, [])
                        if name not in aList:
                            aList.append(name)
                        d[kind] = aList
                else:
                    j = i
            i = j
        else:
            i += 1
        assert progress < i
    return d
#@+node:ekr.20181019042808.11: *5* get_settings & helper
def get_settings(self):
    '''Return a dict containing a representation
    of all settings in leoSettings.leo.
    '''
    trace = False
    c, d = self.settings, {}
    print('scanning: %s' % c.shortFileName())
    settings_node = g.findNodeAnywhere(c, '@settings')
    if not settings_node:
        return self.error('no @settings node')
    for p in settings_node.subtree():
        if self.is_setting(p):
            kind, name = self.parse_setting(p)
            if name:
                # name = self.munge(name)
                aList = d.get(kind, [])
                if name not in aList:
                    aList.append(name)
                d[kind] = aList
            else:
                self.error('no name for %s' % (kind))
    if trace:
        keys = list(d.keys())
        for key in sorted(keys):
            print(key)
            aList = d.get(key)
            for name in sorted(aList):
                print('  ' + name)
    return d
#@+node:ekr.20181019042808.12: *6* is_setting
def is_setting(self, p):
    # For now, these are enough
    table = (
        '@bool', '@color', '@int', '@float',
        # '@font', # special case.
        '@ratio', '@path', '@string',
    )
    for s in table:
        if g.match_word(p.h, 0, s):
            return True
    return False
#@+node:ekr.20181019042808.13: *6* parse_setting
def parse_setting(self, p):
    s = p.h
    assert s[0] == '@'
    i = g.skip_id(s, 1)
    kind = s[: i]
    assert kind
    i = g.skip_ws(s, i)
    j = g.skip_id(s, i, chars='-')
    name = s[i: j]
    return kind, name
#@+node:ekr.20181019042808.14: *5* munge
def munge(self, s):
    '''Return the canonicalized name for settings and arguments to c.config.getX.'''
    return g.toUnicode(s.replace('-', '').replace('_', '').lower())
#@+node:ekr.20181019042808.15: *5* run
def run(self):
    g.cls()
    self.get_commanders()
    configs = self.get_configs()
    settings = self.get_settings()
    if self.errors == 0:
        self.check(configs, settings)
    g.trace('done')
#@+node:ekr.20181020064400.1: *3* @ignore at-button nodes in leoPlugins.leo
#@+node:ekr.20181020064321.1: *4* @button make-md-test
'''Make an @test node for the importer whose name is given.'''
name = 'markdown'
d = g.app.permanentScriptDict
tag = 'make_test_%s_n' % name
n = d.get(tag, 0) + 1
d [tag] = n
d = {'cap_name': name.capitalize(), 'n': n, 'name': name}
<< define preamble >>
p = c.lastTopLevel().insertAfter()
p.b = preamble
p.h = '@test %(name)s importer-%(n)s' % d
c.redraw()
c.selectPosition(p)
c.bodyWantsFocusNow()
c.frame.body.wrapper.setInsertPoint(len(p.b))
#@+node:ekr.20181020064321.2: *5* << define preamble >>
preamble = '''\
if 1:
    # The preamble...
    g.cls()
    if c.isChanged(): c.save()
    import leo.plugins.importers.linescanner as linescanner
    import leo.core.leoImport as leoImport
    import leo.plugins.importers.markdown as markdown
    # Reload all.
    import imp
    imp.reload(linescanner)
    imp.reload(markdown)
    imp.reload(leoImport)
    ic = leoImport.LeoImportCommands(c)
    x = markdown.Markdown_Importer(ic, atAuto=False)   
# insert test for %(name)s here.
''' % d
#@+node:ekr.20160308173429.1: ** Docstrings
#@+node:ekr.20120525155800.10870: *3* Print all docstrings from a module (obsolete)
import leo.core.leoTest as leoTest
import types

def isClass(obj):
    if g.isPython3:
        return isinstance(obj,type)
    else:
        return type(obj) == types.ClassType

specialDictNames = ('__builtins__','__doc__','__name__','__file__','__module__')

def printDoc(x,s):
    if hasattr(x,"__doc__") and x.__doc__:
        g.pr("%4d %s" % (len(x.__doc__),s))
    else:
        g.pr("%4s %s" % (' ',s))

g.pr('-' * 60)
g.pr("%4d %s" % (len(leoTest.__doc__),"leoTest"))

if 1:
    for s in leoTest.__dict__:
        if s not in specialDictNames:
            x = getattr(leoTest,s)
            if type(x) != types.ModuleType:
                printDoc(x,s)
                # if type(x) == types.ClassType:
                if isClass(x):
                    for s2 in x.__dict__:
                        x2 = getattr(x,s2)
                        if s2 not in specialDictNames:
                            g.pr(' '*4,)
                            printDoc(x2,s2)
else:
    << print names sorted by type >>
#@+node:ekr.20120525155800.10871: *4* << print names sorted by type >>
for theType,typeName in (
    (types.ModuleType,"modules"),
    (types.ClassType,"classes"),
    (types.FunctionType,"functions"),
):

    g.pr("\n%s..." % typeName)
    for s in leoTest.__dict__:

        if s not in specialDictNames:
            x = getattr(leoTest,s)
            if type(x) == theType:
                printDoc(x,s)
                if theType == types.ClassType:
                    g.pr("\tmethods...")
                    for s2 in x.__dict__:
                        x2 = getattr(x,s2)
                        if s2 not in specialDictNames:
                            g.pr("\t",newline=False)
                            printDoc(x2,s2)
#@+node:ekr.20161021172753.1: *3* Print commands & docstrings
'''print a summary, sorted by class, of commands & their docstrings.'''
g.cls()
print_class = False
    # Print class in Full mode.
    # False is useful for spell-checking docstrings.
full = True
    # True: print entire docstring. False: print summary.
d = c.commandsDict
    # Keys are command names; values are functions

# Group commands by class.
groups = {}
for command in sorted(d.keys()):
    f = d.get(command)
    key = f.__self__.__class__.__name__ if hasattr(f,'__self__') else f.__name__
    aList = groups.get(key,[])
    aList.append((command,f),)
    groups[key] = aList

# Print groups.
if full:
    root = p.insertAsLastChild()
    root.h = 'Docstrings'
for group in sorted(groups.keys()):
    if full:
        parent = root.insertAsLastChild()
        parent.h = group
    else:
        print('GROUP %s...' % (group))
    aList = groups.get(group)
    for command,f in sorted(aList):
        fname = f.__name__ or ''
        doc = f.__doc__ or ''
        if full:
            lines = g.splitLines(doc)
            if len(lines) == 1:
                s = "'''%s'''" % doc
            else:
                s = ''.join([z.lstrip() for z in g.splitLines(doc)])
                s = s.rstrip() + '\n'
                s = "'''\n%s'''" % s
            if print_class:
                s = '%s\n%s' % (fname,s)
            child = parent.insertAsLastChild()
            child.h = command
            child.b = s
        else:
            print('%40s:%4s %s' % (command,len(doc),fname))
if full:
    c.redraw()
print('%s commands' % (len(list(d.keys()))))
#@+node:ekr.20120525155800.10867: *3* Print missing docstrings (obsolete)
'''print all commands that lack docstrings.'''
g.cls()
d = c.commandsDict
    # Keys are command names; values are functions

ignore = ['minibufferCallback','enterModeCallback',]

aList = [ d.get(z).__name__ for z in d
    if (not d.get(z).__doc__ or not d.get(z).__doc__.strip())
        and not d.get(z).__name__ in ignore]

# g.cls()
print()
for z in sorted(set(aList)):
    print(z)
    
print('done')
#@+node:ekr.20150416062014.1: ** Executing foreign and external scripts
# This includes executing body text in other languages than Python.
#@+node:ekr.20080221113205: *3* @@button print g.es stats
@first # -*- coding: utf-8 -*-

# To do: put list of to-be-translated strings in a doc part.

<< docstring >>
<< version history >>

class controllerClass:
    @others

x = controllerClass(c,p)
x.main()
#@+node:ekr.20080221113205.1: *4* << docstring >>
'''
This script discovers all g.es statements and the unbound variables in
those statements in a single pass over the selected outline. It then puts
the decls followed by the statements in the body text of the
@scan_g.es_results node.

Thus, after this script, you can **execute** the body text of the
@scan_g.es_results node. In particular, assuming that g.translateString
converts all translated text to upper case, it is easy to tell which
strings have been translated.
'''
#@+node:ekr.20080221113205.2: *4* << version history >>
@nocolor
@

2008/2/19 EKR: Initial 2-pass version.
2008/2/20 EKR:
- Convert to single-pass using decls and statements arrays.
- Add 'tabName' to list of keyword arguments.
- Fixed various bugs.
2008/2/20 EKR 2:
- Generate g.es statements to inicate beginnings of files.
  This allows us to execute all generated statements at once.
- Convert between '.' and '__' as needed to avoid using real ivars.
2008/2/21 EKR:
- Added mungeString.
- Rewrote mungeStatements, scanParens and scanIdChain.
These changes now properly handle all arguments.
2008/2/21 EKR 2:
- Added stringize and related logic.
- Added setToBeTranslated to put list of to-be-translated strings into leading docstring.
#@+node:ekr.20080221113205.3: *4*  ctor
def __init__ (self,c,p):

    self.c = c
    self.p = p

    self.decls = {}
    self.keywordArgs = ('color','newline','tabName',)
    self.nodeName = '@scan_g.es_results'
    self.statements = []
    self.printStatements = ('g.es','g.es_print',)
    self.suppressTranslation = False # True: keyword arg to g.es seen.
    self.toBeTranslated = []
    self.traceFlag = False

    self.root = g.findNodeAnywhere(c,self.nodeName)
#@+node:ekr.20080221113205.4: *4* error & trace
def error (self,s):

    self.statements.append('***error: %s' % s)

def trace(self,s):

    if self.traceFlag:
        self.statements.append(s)
#@+node:ekr.20080221113205.5: *4* main
def main (self):

    '''The main line.'''

    c = self.c
    self.scan()
    self.mungeStatements()
    keys = self.decls.keys()
    keys.sort()
    # This is such a great hack :-)
    decls = ["%s = '%s'" % (self.mungeString(z),self.stringize(z,delim="'")) for z in keys]

    # Put to-be-translated.
    results = ['@ To be translated...',]
    self.toBeTranslated.sort()
    results.extend(self.toBeTranslated)
    results.extend(['@c',])
    # Put decls.
    results.extend(['# Decls...',])
    results.extend(["color='red'","newline=True","tabName='Log'"])
    results.extend(decls)
    # Put statements
    results.extend(["g.app.translateToUpperCase=True",])
    results.extend(self.statements)
    results.extend(["g.app.translateToUpperCase=False",])

    results = '\n'.join(results)

    # Put the results in the root node, or print them.
    if self.root:
        c.setBodyString(self.root,results)
        c.selectPosition(self.root)
        c.redraw()
    else:
        print 'node not found',self.rootName
        print results

    g.es('done')
#@+node:ekr.20080221113205.6: *4* munging...
#@+node:ekr.20080221113205.7: *5* mungeString
def mungeString (self,s):

    '''Convert string s so that it is a valid Python name.'''

    s = s.strip()
    # s = s.replace('@','_AT_')
    # s = s.replace('<','_LT_')
    # s = s.replace('>','_GT_')
    s = s.replace(',','_COMMA_')
    s = s.replace('.','_DOT_')
    s = s.replace('()','_PARENS_')
    s = s.replace('(','_LP_')
    s = s.replace(')','_RP_')
    s = s.replace('"','_DQ_')
    s = s.replace("'",'_SQ_')
    s = ''.join([g.choose(z == '_' or z.isalnum(),z,'_') for z in s])
    return s
#@+node:ekr.20080221113205.8: *5* mungeHeadline
def mungeHeadline (self,s):

    s = s.strip()
    s = s.replace('<<','< <')
    s = s.replace('>>','> >')
    return s
#@+node:ekr.20080221113205.9: *5* mungeStatements
def mungeStatements (self):

    '''Convert a references to an known id chain by the corresponding decl.'''

    keys = self.decls.keys()
    for tag in self.printStatements:
        if tag in keys: keys.remove(tag)

    results = []
    for s in self.statements:
        i = 0 ; inner_result = []
        while i < len(s):
            progress = i
            for key in keys:
                if g.match_word(s,i,key):
                    inner_result.append(self.mungeString(key))
                    i += len(key)
                    break
            else:
                inner_result.append(s[i])
            i = max(i,progress+1)
        new_statement = ''.join(inner_result)
        if new_statement != s:
            # Add the original lines as comments.
            for z in g.splitLines(s):
                results.append ('# %s' % (z.rstrip()))
        results.append(new_statement) # Add the munged line.

    self.statements = results

#@+node:ekr.20080221113205.10: *5* stringize
def stringize (self,s,delim):

    s = s.strip()
    s = s.replace(delim,'\\%s' % (delim))
    return s
#@+node:ekr.20080221113205.11: *4* scan & helpers
def scan(self):


    for p in self.p.self_and_subtree():

        self.scanNode(p)
#@+node:ekr.20080221113205.12: *5* scanIdChain
def scanIdChain (self,s,i,outer):

    assert g.is_c_id(s[i])
    start = i 

    while i < len(s):
        progress = i
        # Skip a.b.c...
        if not g.is_c_id(s[i]): break
        i = g.skip_id(s,i,chars='.')
        i = g.skip_ws(s,i)
        word = s[start:i].strip()
        if outer and g.match(s,i,'=') and word in (self.keywordArgs):
            # g.trace('***','=',word)
            self.suppressTranslation = True
        # Only a paren is valid now.
        if not g.match(s,i,'('): break
        # We shall munge the entire paren expression.
        i = g.skip_matching_python_parens(s,i)
        if g.match(s,i,')'): i += 1
        # Only a dot is valid now.
        if not g.match(s,i,'.'): break
        i += 1
        assert i > progress

    self.setDecl(s[start:i])
    return i
#@+node:ekr.20080221113205.13: *5* scanNode
def scanNode(self,p):

    tags = ('g.es','g.es_print',)
    s,h = p.b, p.h
    print_h = True

    if h.startswith('@thin'):
        << insert statements highlighting the @thin node >>

    i = 0
    while i < len(s):
        progress = i
        for tag in tags:
            if g.match_word(s,i,tag):
                start = i ; i += len(tag)
                self.suppressTranslation = False
                i = g.skip_ws(s,i)
                if g.match(s,i,'('):
                    << Insert a comment containing the headline >>
                    i = self.scanParens(s,i,outer=True)
                    statement = s[start:i]
                    self.statements.append(statement)
                break
        i = max(i,progress+1)
#@+node:ekr.20080221113205.14: *6* << insert statements highlighting the @thin node >>
self.statements.append('# ---- %s' % (h))
self.statements.append("g.es_print('','%s %s',color='red')" % ('-'*20,h))
#@+node:ekr.20080221113205.15: *6* << Insert a comment containing the headline >>
if print_h:
    print_h = False
    if not h.startswith('@thin'):
        self.statements.append('# -- node %s' % (self.mungeHeadline(h)))
#@+node:ekr.20080221113205.16: *5* scanParens
def scanParens(self,s,i,outer):

    '''Scan a parenthesized list of args.'''

    assert g.match(s,i,'(')
    j = g.skip_matching_python_parens(s,i)
    if j == -1:
        self.error('**incomplete paren at: %s' % s[i:])
        return i+1 # Just skip the paren

    assert g.match(s,j,')')
    n = 1
    while i < j:
        progress = i ; ch = s[i]

        if ch in ("'",'"',):
            i = self.scanStringArg(s,i,n,outer)
            n += 1

        elif g.is_c_id(ch):
            i = self.scanIdChain(s,i,outer)
            n += 1

        i = max(i,progress+1)

    return j + 1 # return the character after the paren.
#@+node:ekr.20080221113205.17: *5* scanStringArg
def scanStringArg(self,s,i,n,outer):

    '''Skip a string, and possible %'''

    j = g.skip_python_string(s,i)
    if j <= i: return i

    # g.trace(n,s[i:j])

    if outer:
        self.setToBeTranslated(s[i:j],n)

    i = g.skip_ws(s,j)
    if not g.match(s,i,'%'):
        return i

    # Handle what follows the '%'
    i = g.skip_ws(s,i+1)

    if g.match(s,i,'('):
        i = self.scanParens(s,i,outer=False)
    elif i < len(s) and g.is_c_id(s[i]):
        i = self.scanIdChain(s,i,outer)

    return i
#@+node:ekr.20080221113205.18: *4* setDecl
def setDecl (self,s):

    s = s.strip()

    if s in ('c','g','p','len','str','repr','and','or','not','is',):
        return # Don't need a binding for these.

    if s in ('color','newline','tabName',):
        return # Special binding will be used for these.

    if s.isdigit():
        return # Don't bind integers.

    self.decls[s] = True
#@+node:ekr.20080221113205.19: *4* setToBeTranslated
def setToBeTranslated (self,s,n):


    s = s.strip()

    if s.startswith('"') or s.startswith("'"):
        s = s[1:]
    if s.endswith('"') or s.endswith("'"):
        s = s[:-1]

    s = s.strip()

    # Suppress translation when a keyword is seen, regardless of position.
    if s in self.keywordArgs:
        self.suppressTranslation = True

    if self.suppressTranslation:
        # g.trace('not added',s)
        return

    if (n % 2) == 1 and s not in self.toBeTranslated:
        self.toBeTranslated.append(s)
#@+node:maphew.20130607222534.1739: *3* @@button Run Elevated
''' An experimental leo button which executes the currently selected node as a
python script after invoking User Account Control (UAC).

You could easily bork all your data or even your computer with this: it runs
code with admin privileges, no sanity checking, and very limited feedback. By
default tracebacks etc. are silently swallowed. The only feedback you'll get is
what you explicitly add, and only to gui widgets or files on disk at that, as
Windows has probably hidden the console window.

    1. Copy and paste the full text from below into any Leo outline pane 
    2. save and reopen the workbook to create the button 
    3. navigate to a node and push the [run-elevated] button, but for heavens
        sake be careful!
    
Limitations: 
    - if Leo was started with pythonw.exe instead of python.exe there
    is zero feedback for syntax errors etc. 
    - the node script must not have any doublequotes -- "
    - there may be length limitations, likely about 8k characters
'''
@others
#@+node:maphew.20130608014320.1743: *4* imports
import os
import sys
#@+node:maphew.20130608014320.1744: *4* Leo preparation
g.es('Run-elevated on:', p.h)

# -i : ask python interpreter to stay open when done, to see messages
# -c : pass body of current node to python as command line script
params = r'-ic "{}" '.format(p.b)
# g.es(params)
#@+node:maphew.20130608014320.1745: *4* UAC Elevation
import ctypes
import tempfile

hwnd = 0                # parent window
lpOperation = 'runas'   # force elevated UAC prompt
lpFile = sys.executable # path to python
lpFile = lpFile.replace('pythonw.exe', 'python.exe') # force console python, only way to see messages
lpParameters = params   # arguments to pass to python
lpDirectory = tempfile.gettempdir() # working dir
nShowCmd = 1            # window visibility, must be 1 for Leo.

print(lpFile, lpParameters)
g.es(lpFile, lpParameters)
retcode = ctypes.windll.shell32.ShellExecuteA(hwnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd)
msg = 'Exit code: {0} - {1}'.format(retcode, ctypes.FormatError(retcode))
print(msg)
g.es(msg)

@
Full command executed when script is complete will resemble:

C:\Python27\python.exe -ic "import \ntestfile = 'foo'\n...\n#end of file"
#@+node:maphew.20130608014320.1742: *4* Resources
# adapted from:

# @url http://blog.pythonaro.com/2011/09/python-wmi-services-and-uac.html
# @url http://stackoverflow.com/questions/130763/request-uac-elevation-from-within-a-python-script
# @url http://pypeelf.googlecode.com/svn/branches/third-party/winappdbg-1.3/winappdbg/win32/shell32.py

# Launching Applications (ShellExecute, ShellExecuteEx, SHELLEXECUTEINFO)
# @url http://msdn.microsoft.com/en-us/library/windows/desktop/bb776886%28v=vs.85%29.aspx
#@+node:ekr.20050225161940: *3* Execute @test, @suite or file
@

To run the script:

1. Make the 'Exec File' node a script button.

2. Select either the @test node or the @thin node.

(You must change @@thin to @thin for this to work.)

3. Hit the Exec File button.
#@+node:ekr.20050225161940.1: *4* @test __name__
assert __name__ == '__builtin__', '__name__ is %s' % __name__
#@+node:ekr.20050225162123: *4* @@thin executeFileTest.py
@language python
@tabwidth -4

print __name__
#print 'hi'

assert __name__ == '__main__', '__name__ is %s' % __name__
#@+node:ekr.20050225161940.2: *4* Exec File
<< docstring >>
<< imports >>

@others

doatest()
#@+node:ekr.20050225161940.3: *5* << docstring >>
"""this script will run python from Leo with @file somefile.py
using the py2.4 (2.3 compatible) subprocess module
to verify and code correct in the quickest possible manner.
unittests or other w/stdout stderr redirected to log.
wimped out for the moment on making it virtual.

If the presently selected node is an @test or @suite node this script runs them,
otherwise the script expects the node to a valid @file node that has been
written to disk and updated.

less complicated and thus more advanced unittesting:
py.test, similar to unittest as far as adding support.
and @test and @suite, many examples in test.leo
and my own dutest in dyna_menu with redirection to log
which calls leoTest.py on @test and @suite nodes 
otherwise for doctesting w/o needing an @file written.

good overview, see Recent Posts:
http://agiletesting.blogspot.com
http://agiletesting.blogspot.com/2005/01/
Python unit testing part 3: the py.test tool and library 
python-unit-testing-part-2-doctest part-1 unittest

tested win9x Leo4.3a2 py2.4 2.3
tested but little and probably not completely.
if it works once its done, right?
make an @button or add to a menu or plugin

Note: 
you may see some extraneous traceback 
vrs run outside Leo, can't be helped.

Warning: this may run arbitrary code.
no warranty expressed or implied!
"""
#@+node:ekr.20050225171553: *5* << imports >>
import leoTest

try:
    import subprocess # Exists only in Python 2.4.
except ImportError:
    subprocess = None

import os
import StringIO
import sys
import unittest
#@+node:ekr.20050225161940.5: *5* getAtFileName
def getAtFileName():

    '''Return the full path from any kind of @file node and applicable @path directives.'''

    # should return cwd, cd to files path
    # script should return cwd after run

    fname = p.anyAtFileNodeName()
    if fname:
        d = c.scanAllDirectives()
        path = d.get("path")
        fname = g.os_path_abspath(g.os_path_normpath(g.os_path_join(path, fname)))

    return fname
#@+node:ekr.20050225161940.6: *5* doatest
def doatest():

    _sosav = sys.__stdout__
    _sesav = sys.__stderr__
    sys.stdout = sys.__stdout__ = g.fileLikeObject()
    sys.stderr = sys.__stderr__ = g.fileLikeObject()

    h = p.h
    if h.startswith('@suite') or h.startswith('@test'):
        leoTest.doTests(all= False,verbosity=2)
    else: # Try to run an @file unittest.
        name = getAtFileName()
        if name:
            g.executeFile(name,'-v')
        else:
            g.es('Expecting @test, @suite or any @file node',color='blue')

    oo = sys.stdout.get()
    oe = sys.stderr.get()
    sys.stdout.close()
    sys.stderr.close()
    sys.stdout = sys.__stdout__ = _sosav
    sys.stderr = sys.__stderr__ = _sesav
    lines = g.splitLines(oo + oe)
    empty = True
    for line in lines:
        if line.rstrip():
            empty = False
    if lines and not empty:
        g.es('Output from test...',color='blue')
        for line in lines:
            g.es(line.rstrip())
    g.es('Test done',color='blue')

    c.frame.putStatusLine(' fini ', color= 'DodgerBlue')
#@+node:ekr.20120527100809.12771: *3* Prototype: jinja @command nodes
#@+node:ekr.20120527100809.12797: *4* jinja test
@others
#@+node:ekr.20120527100809.12798: *5* node 1
Hello again, {{ name }}.

Your rank is {{rank}} and your tag number is {{serial_number}}.
#@+node:ekr.20120527100809.12804: *4* @common jinja code
def run(fn,h,d):
    '''Write the template in node with headline h
    using data in dict d to the file whose name is fn.
    '''
    from jinja2 import Template
    p = g.findNodeAnywhere(c,h)
    if p:
        s = g.getScript(c,p,useSelectedText=False,useSentinels=False)
        s = Template(s).render(d)
        g.trace(s)
    else:
        g.trace('not found',h)
#@+node:ekr.20120527100809.12805: *4* write jinja (prototype of @command node)
exec(g.findTestScript(c,'@common jinja code'))
d = {'name':'Fred','rank':'colonel','serial_number':'3.14159'}
run('fn','jinja test',d)
#@+node:ekr.20060227124153: *3* Run a Lua program
# http://sourceforge.net/forum/message.php?msg_id=3581176
# By: e

"""
run lua program
work alike for perl too

edit in path to lua if required
make a script button


no fallback for no subprocess
download from eff.bot.org 
if you are on python <2.4

added option to pipe script to stdin

todo:
if your os can fork, use a fork.
"""

import subprocess as sub
from dynacommon import stripSentinels
import sys

if sys.platform[:3] == 'win':
    ex = 'c:/UTIL/lua.exe'
else:
    ex = '/usr/bin/lua'

script = g.getScript(c, p, forcePythonSentinels= False)
#print script

source = stripSentinels(script)
#print source

def run_cmd(cmdlst, tostdin=None):
    ps = sub.Popen(cmdlst, 
        universal_newlines= True,
        stdout= sub.PIPE, stderr= sub.PIPE,
        stdin= sub.PIPE)
    (outstd, outerr) = ps.communicate(tostdin)
    return (ps.wait(), outstd, outerr)

# send complete script on stdin
# many comandline programs have this same option.
ret,sto,sde = run_cmd([ex, '-'], source)

if not ret:
    print sto
if sde: print sde
#@+node:maphew.20130608014320.1741: *3* Run Elevated test
import os, sys
testfile = 'c:/windows/__delete_this_file__'
f = open(testfile,'w')
f.write('this is a dummy file for Leo run-elevated button script, please get rid of it\n')
f.close()

print('Look for {} and remove it. If it does not exist, the test has failed'.format(testfile))
os.startfile('c:/windows')
#@+node:ekr.20110518140548.19090: *3* Run Leo scripts with various Python interpreters
#@+node:ekr.20110518140548.19104: *4* Description
@language rest
@wrap

This code contributed by Kent Tenney.

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

I've had endless problems with interpreter versioning, leading me create the Runwith class. It writes a file to disk, makes it executable, runs it, captures exitcode, err and output, removes the files, provides reports.

This provides complete decoupling from Leo.

I have run-bash and run-python buttons in Leo which run the node contents as shell or python.

I'm liking it a lot.

I'm currently extending it to provide command line sugar:

$ rw ln -s /home/ktenney/work/src/project/bin/myscript
/home/ktenney/bin --makelink.sh--

The "rw" script executes the command, and creates the script makelink.sh

The following is Outline -> Copy Node version of the Runwith class

It does some data gathering which probably don't apply: hashes of the code,
hostname, timestamps. These get stored in json.
#@+node:ekr.20110518140548.19105: *4* class Runwith
class Runwith(object):
   """Generate and manage scripts.

   >>> import getatdata

   """
   import tempfile
   import os
   import platform
   import json
   import md5
   import time
   import subprocess
   @others
#@+node:ekr.20110518140548.19106: *5* __init__
def __init__(self, interpreter, code,
            cleanup=True, autorun=True,
            ):
   """Init a Runwith object, will run ``code`` using ``interpreter``

   >>> import getatdata
   >>> rw = getatdata.Runwith('/bin/bash', 'ls -la /tmp')
   >>> rw.get_runfile()
   '/tmp/...

   >>> rw.get_runfile('/tmp/listing')
   '/tmp/listing'

   """

   self.interpreter = interpreter
   self.code = code
   self._normalize_code()
   self.cleanup = cleanup
   self.fbase = self.tempfile.mktemp()
   self.runfile = self.fbase + '.run'
   self.outfile = self.fbase + '.out'
   self.errfile = self.fbase + '.err'
   self.jsonfile = self.fbase + '.json'
   self.fileset = [self.outfile, self.errfile, self.runfile, self.jsonfile]
   self.hostname = platform.node()
   if autorun:
       self.run()

#@+node:ekr.20110518140548.19107: *5* _prepare_run
def _prepare_run(self):
   """
   """

   self.hashbang = "#!" + self.interpreter
   contents = self.hashbang + "\n" + self.code + "\n"
   with file(self.runfile, 'w') as f:
       f.write(self.hashbang + '\n' + self.code + '\n')
       f.close()
       self.os.chmod(self.runfile, 0500)
#@+node:ekr.20110518140548.19108: *5* _prepare_jsonfile
def _prepare_jsonfile(self):
   """
   """

   with file(self.jsonfile, 'w') as f:
       f.write(self.json_data)
#@+node:ekr.20110518140548.19109: *5* _normalize_code
def _normalize_code(self):
   """Normalize the spacing in a command line.

   We don't want whitespace variations to result in different hashes for
   the same command.
   """

   # don't try to normalize scripts
   fixed_code = self.code.strip()
   if '\n' in fixed_code:
       self.multiline = True

   else:
       self.multiline = False
       # normalize the command line
       chunks = self.code.split()
       fixed_code = ' '.join(chunks)

   self.code = fixed_code

   code_hash_obj = self.md5.md5(fixed_code)
   self.codehash = code_hash_obj.hexdigest()
#@+node:ekr.20110518140548.19110: *5* run
def run(self):
   """Do it.

   """

   #create output files for writing
   outfile = file(self.outfile, 'w')
   errfile = file(self.errfile, 'w')
   self._prepare_run()
   self.begintime = self.time.time()
   self.exitcode = self.subprocess.Popen(self.runfile, shell=True,
                             stdout=outfile, stderr=errfile).wait()
   self.endtime = self.time.time()
   outfile = file(self.outfile)
   errfile = file(self.errfile)
   self.out = outfile.read()
   self.err = errfile.read()
   outfile.close()
   errfile.close()
   self._report()
   if self.cleanup:
       self._cleanup()
#@+node:ekr.20110518140548.19111: *5* _report
def _report(self):
   """
   """

   rundict = {'exitcode':self.exitcode,
              'stdout':self.out, 'stderr':self.err}
   rundict['code'] = self.code
   rundict['hashbang'] = self.hashbang
   rundict['hostname'] = self.hostname
   rundict['codehash'] = self.codehash
   humantime = self.time.ctime(self.endtime)
   timestamps = {'begintime':self.begintime, 'endtime':self.endtime,
                 'humantime':humantime}
   rundict['timestamps'] = timestamps

   self.rundict = rundict
   self.json_data = self.json.dumps(rundict)

#@+node:ekr.20110518140548.19112: *5* _cleanup
def _cleanup(self):
   """Remove files.

   """

   for f in self.fileset:
       if os.path.exists(f):
           os.remove(f)
#@+node:ekr.20110518140548.19113: *5* get_runfile
def get_runfile(self, fname=None):
   """Create the executable and return it's filename.
   """

   if fname is not None:
       self.runfile = fname

   if fname is not self.runfile:
       self._prepare_run()

   return self.runfile



#@+node:ekr.20110518140548.19114: *5* get_jsonfile
def get_jsonfile(self, fname='codehash'):
   """Create the json file and return it's filename.
   """

   if fname is not None:
       if fname is 'codehash':
           # name according to the code and run timestamp
           hash = self.codehash
           ts = str(self.rundict['timestamps']['begintime'])
           jsonfile = hash + "-" + ts + ".json"

       else:
           # use the tempfile name
           jsonfile = fname

   dest_dir = self.os.path.dirname(self.fbase)
   self.jsonfile = os.path.join(dest_dir, jsonfile)
   self._prepare_jsonfile()
   return self.jsonfile



#@+node:ekr.20051025070722.1: *3* Running tcl scripts from the body pane
@language tcltk
#@+node:ekr.20051024223801: *4* run tcl
# To use this script, you must make this a script button, either with the
# 'Script Button' button or by making this an @button node.

# Executes body text of selected node as a tcl script.
# For more information, see http://wiki.tcl.tk/6878

# This statement allows you to organize your scripts using noweb markup.
s = g.getScript(c,p,forcePythonSentinels=False)
g.es(g.app.root.tk.eval(s))
#@+node:ekr.20051024223801.1: *4* My first tcl script
# This is a comment
set x 50
return [expr $x*10]
#@+node:ekr.20051025115413: *4* My second tcl scrip, organized with noweb markup
@others
#@+node:ekr.20051025115413.1: *5* the actual script
# This is a comment
set x 50 +
return [expr $x*10]
#@+node:ekr.20150416102234.1: ** Finding & reporting
#@+node:ekr.20120328102352.6945: *3* @@button create specialized find buttons
@language python

<< documentation >>

from leo.plugins.mod_scripting import scriptingController

sc = scriptingController(c)

if c.frame.body.hasSelection():
    code = c.frame.body.getSelectedText()
    heading = 'fix'
else:
    code,heading = p.b,p.h

def transform(c=c,code=code):
    w = c.frame.body
    s = w.getSelectedText()
    g.es(s)
    exec(code)
    g.es(s)
    w.deleteTextSelection()
    i = w.getInsertPoint()
    w.insert(i,s)
    p.b = w.getAllText()
    w.setInsertPoint(i)

sc.createIconButton(
    heading,
    command = transform,
    shortcut = None,
    statusLine = 'Make filter button',
    bg = "LightBlue"
)
#@+node:ekr.20120328102352.6949: *4* << documentation >>
@language rest
@
http://groups.google.com/group/leo-editor/browse_thread/thread/d21349c52dabd066

Ever find that you have a whole lot of:

.. sourcecode:: py

  rec[f['analyte']] ... rec[f['sample_type']] ...

expressions in your code, and now things have changed and you want them all
to be:

.. sourcecode:: py

  row.Analyte ... row.Sample_Type ...

basically if str variable s was::

  rec[f['analyte']]

then you want to perform:

.. sourcecode:: py

  s = "row."+s.split("'")[1].title()

on each one. In general it would be nice to be able to use a python
expression when search and replace doesn't cut it.

The button code below creates a button, ``fac``, which, when pressed,
creates another button, with some name you choose, which, when pressed,
executes some python code to fix the selected text in the body.

You can define the code to be executed in two ways, either in its own node:

 - insert a new node with a headline which describes the refactor
 - enter code in the node which modifies the string variable ``s``,
   which is initially set to the selected text in the body
 - press the ``fac`` button, which creates a new button named
   after this code node
 - select each offending piece of text and press the button created
   in the previous step to fix

or

 - type some code modifying ``s`` right in the body you're working on
 - press the ``fac`` button, which creates a new button named "fix"
 - select each offending piece of text and press the button created
   in the previous step to fix

Notes:

 - unlike regular button nodes, changing the code after the
   button's created (first option above) doesn't change the code
   executed by the button
 - replacing selection text makes Leo reposition the insert point at
   the top of the window, this is annoying but unrelated to this code
#@+node:EKR.20040517074600.8: *3* Count pages
nodes = 0 ; lines = 0
for p in c.all_unique_positions():
    nodes += 1
    lines += len(g.splitLines(p.b))

pages = ((nodes * 10) + lines) / 50
s = "%d nodes,  %d lines, %d pages" % (nodes,lines,pages)
print(s); g.es(s)
#@+node:ekr.20051110105027.104: *3* Count separate nodes
# p = g.findTopLevelNode("Code")

tnodes = {} ; count = 0
for p in p.self_and_subtree():
    tnodes[p.v]=p.v
    count += 1

s = "%4s: %d vnodes, %d distinct" % ("Code",count,len(tnodes.keys()))
g.es_print(s)

tnodes = {} ; count = 0
for p in c.all_positions():
    tnodes[p.v]=p.v
    count += 1

s = "%4s: %d vnodes, %d distinct" % ("All",count,len(tnodes.keys()))
g.es_print(s)
#@+node:ekr.20051110105027.105: *3* Count total, visible nodes
total,visible = 0,0

for p in c.all_positions():
    total += 1

p = c.rootPosition()
while p:
    visible += 1
    p.moveToVisNext(c)

print "total,visible",total,visible
#@+node:ekr.20070930042719: *3* Create @menus tree from menu tables
'''Create @menus tree from Leo's internal menu tables.'''
# Convert standard tables to list of @item nodes
m = c.frame.menu
# A representation of code/data in defineMenuTables and createMenusFromTables.
@others
data = (
    ('File',[
        (None,m.fileMenuTopTable),
        ('Open &With...',[]), ###
        (None,m.fileMenuTop2Table),
        ('&Read/Write...',m.fileMenuReadWriteMenuTable),
        ('Tan&gle...',m.fileMenuTangleMenuTable),
        ('&Untangle...',m.fileMenuUntangleMenuTable),
        ('&Import...',m.fileMenuImportMenuTable),
        ('&Export...',m.fileMenuExportMenuTable),
        (None,m.fileMenuTop3MenuTable),
        ]),
    ('Edit',[
        (None,m.editMenuTopTable),
        ('Edit &Body...',m.editMenuEditBodyTable),
        ('Edit &Headline...',m.editMenuEditHeadlineTable),
        ('&Find...',m.editMenuFindMenuTable),
        (None,m.editMenuTop2Table),
        ]),
    ('Outline',[
        (None,c.frame.menu.outlineMenuTopMenuTable),
        ('Chec&k...',m.outlineMenuCheckOutlineMenuTable),
        ('E&xpand/Contract...',m.outlineMenuExpandContractMenuTable),
        ('&Move...',m.outlineMenuMoveMenuTable),
        ('M&ark...',m.outlineMenuMarkMenuTable),
        ('&Go To...',m.outlineMenuGoToMenuTable),
        ]),
    ('Plugins',[
        ]), # A placeholder.
    ('Cmds',[
        ('&Abbrev...',m.cmdsMenuAbbrevTable),
        ('Body E&ditors...',m.cmdsMenuBodyEditorsTable),
        ('&Buffers...',m.cmdsMenuBuffersTable),
        ('&Chapters...',m.cmdsMenuChaptersTable),
        ('C&ursor/Selection...',[]), ### Has several submenus...Must be placed by hand.
            ('Cursor &Back...',m.cursorMenuBackTable),
            ('Cursor Back &Extend Selection...',m.cursorMeuuBackExtendTable),
            ('Cursor Back Extend &to...',m.cursorMenuExtendTable),
            ('Cursor &Forward...',m.cursorMenuForwardTable),
            ('Cursor Forward E&xtend Selection...',m.cursorMenuForwardExtendTable),
        ('&Focus...',m.cmdsMenuFocusTable),
        ('&Macro...',m.cmdsMenuMacroTable),
        ('M&inibuffer...',m.cmdsMenuMinibufferTable),
        ('&Pickers...',m.cmdsMenuPickersTable),
        ('&Rectangles...',m.cmdsMenuRectanglesTable),
        ('Re&gisters...',m.cmdsMenuRegistersTable),
        ('R&un Script/Tests...',m.cmdsMenuRunTable),
        ('Scr&olling...',m.cmdsMenuScrollTable),
        ('Spell C&heck...',m.cmdsMenuSpellCheckTable),
        ('&Text Commands...',m.cmdsMenuTextTable),
        ('Toggle Setti&ngs...',m.cmdsMenuToggleTable),
        ]),
    ('Window',[
        (None,m.windowMenuTopTable),
        ]),
    ('Help',[
        (None,m.helpMenuTable),
        ]),
)

for menuName,tables in data:
    # print menuName,tables
    p2 = p.insertAsLastChild()
    p2.initHeadString('@menu '+menuName)
    for aTuple in tables:
        subMenuName,aList = aTuple
        if subMenuName:
            p3 = p2.insertAsLastChild()
            p3.initHeadString('@menu '+subMenuName)
        else:
            p3 = p2
        for z in aList:
            p4 = p3.insertAsLastChild()
            setNode(p4,z)
c.redraw()
#@+node:ekr.20070930042719.1: *4* setNode
def setNode (p,data):

    head = body = None

    if type(data) == type('abc'):
        head = data
    elif type(data) in (type(()),type([])):
        if   len(data) == 1: head = data[0]
        elif len(data) == 2: body, head = data
        else: g.trace('bad tuple: ',repr(data))
    else: g.trace('bad data: ',repr(data))

    if head and head.strip():
        p.initHeadString('@item ' + head.strip())
    if body and body.strip():
        p.setTnodeText(body.strip())
#@+node:ekr.20130810093044.16971: *3* Display function call hierarchy in Leo
'''
From Brian Theado

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

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

import trace

# http://docs.python.org/library/trace.html documents trace module.
tracer = trace.Trace(countcallers=1)

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

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

top = p.insertAsLastChild().copy()
top.h = 'trace session'
displayCalltree(top, tracer.results().callers.keys())
c.redraw()

@language python
@others
#@+node:ekr.20130810093044.16972: *4* displayCalltree
def displayCalltree(p, callinfo):
   '''
   Converts the function call hierarchy in 'callinfo' into a tree of function
   calls.  The function call tree is displayed to stdout as indented text
   and is inserted as a tree of leo nodes rooted at the given position 'p'
   '''
   callers = [k[0] for k in callinfo]
   callees = [k[1] for k in callinfo]

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

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

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

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

       # Ascend back up one level
       path = path[0:-1]
       p = p.parent()
       levels = levels[0:-1]
#@+node:ekr.20130810093044.16973: *4* trace session
#@+node:ekr.20040311090054: *3* Dump fileIndex
for p in c.all_positions():
    print p.v.fileIndex
#@+node:ekr.20060801093639: *3* Find @file nodes
current = c.currentPosition()
for p in current.self_and_parents():
    if p.isAnyAtFileNode():
        d = c.scanAllDirectives(p)
        path = d.get('path')
        print g.os_path_join(path,p.atFileNodeName())
        break
#@+node:ekr.20130920214241.12461: *3* Find all changed methods
# The list of all changed methods in the grand reorg at revs 6016-6020
aList = [
'color','configureBorder','configureFont',
'createBindings','createFindPanel','createFindTab','createFrame',
'createRootWindow',
'disable','enable','forceLogUpdate'
'getFont','getFontConfig','getFrame',
'headWidth','interrupt','isEnabled','isSameColorState',
'kill','killGui','killPopupMenu',
'onActivate','onActivateLog',
'recreateRootWindow','restoreAllState',
'saveAllState','setBindings','setCanvasBindings','setColorFromConfig',
'setDisabledHeadlineColors','setEditHeadlineColors','setEditLabelState',
'setFocus','setFont','setFontFromConfig',
'setMinibufferBindings','setTabBindings',
'setUnselectedHeadlineColors','setUnselectedLabelState',
'setWidgetFontFromConfig',
'widthInPixels',
]
g.cls()
seen = set()
p = g.findNodeAnywhere(c,'Plugins')
for p in p.subtree():
    for name in aList[:]:
        i = 0
        s = p.b
        while i < len(s):
            progress = i
            i = p.b.find('.'+name,i)
            if i == -1: break
            if g.match_word(s,i,'.'+name):
                print('%s %s' % (name,p.h))
                seen.add(name)
                aList.remove(name)
                break
            else:
                i += 1
            assert progress < i,(i,progress)
for z in sorted(seen):
    print(z)
print('**done')
#@+node:ekr.20130810093044.16955: *3* Find all comments from modes (slow)
@language python

'''Slow script.'''

import glob
import imp

@others

if 0: # The other script is much faster.
    
    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.20130810093044.16956: *4* 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.20130810093044.16957: *4* 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.20051110105027.159: *3* Find and replace all functions in leoGlobals.py
import string

@others

if 1:
    << set nameList to the list of functions in leoGlobals.py >>
else:
    p = g.findNodeAnywhere("@file leoGlobals.py")
    nameList = findFunctionsInTree(p)

    nameList.sort() ; g.enl()
    for name in nameList: g.es("'%s'," % name)

    s = "%d functions in leoGlobals.py" % len(nameList)
    g.es_print(s)

if 0:
    p = g.findTopLevelNode(c,"Code")
    g.enl() ; g.enl()
    count = prependNamesInTree(p,nameList,"g.",replace=True) # Just prints if replace==False.
    s = "%d --- done --- " % count
    g.es_print(s)
#@+node:ekr.20051110105027.160: *4* findFunctionsInTree
def findFunctionsInTree(p):

    nameList = []
    for p in p.self_and_subtree():
        names = findDefs(p.b)
        if names:
            for name in names:
                if name not in nameList:
                    nameList.append(name)
    return nameList
#@+node:ekr.20051110105027.161: *4* findDefs
def findDefs(body):

    lines = body.split('\n')
    names = []
    for s in lines:
        i = g.skip_ws(s,0)
        if g.match(s,i,"class"):
            return [] # The classes are defined in a single node.
        if g.match(s,i,"def"):
            i = g.skip_ws(s,i+3)
            j = g.skip_c_id(s,i)
            if j > i:
                name = s[i:j]
                if g.match(name,0,"__init__"): 
                    return [] # Disallow other class methods.
                names.append(name)
    return names
#@+node:ekr.20051110105027.162: *4* prependNamesInTree
def prependNamesInTree(p,nameList,prefix,replace=False):

    c = p.c

    assert(len(prefix) > 0)
    ch1 = string.letters + '_'
    ch2 = string.letters + string.digits + '_'
    def_s = "def " ; def_n = len(def_s)
    prefix_n = len(prefix)
    total = 0
    for p in p.self_and_subtree():
        count = 0 ; s = p.b
        printFlag = False
        if s:
            for name in nameList:
                i = 0 ; n = len(name)
                while 1:
                    << look for name followed by '(' >>
            if count and replace:
                if 0:
                    << print before and after >>
                c.setBodyString(p,s)
                p.setDirty()
        g.es("%3d %s" % (count,p.h))
        total += count
    c.redraw()

    return total
#@+node:ekr.20051110105027.163: *5* << look for name followed by '(' >>
i = s.find(name,i)
if i == -1:
    break
elif g.match(s,i-1,'.'):
    i += n # Already an attribute.
elif g.match(s,i-prefix_n,prefix):
    i += n # Already preceded by the prefix.
elif g.match(s,i-def_n,def_s):
    i += n # preceded by "def"
elif i > 0 and s[i-1] in ch1:
    i += n # Not a word match.
elif i+n < len(s) and s[i+n] in ch2:
    i += n # Not a word match.
else:
    j = i + n
    j = g.skip_ws(s,j)
    if j >= len(s) or s[j] != '(':
        i += n
    else: # Replace name by prefix+name
        s = s[:i] + prefix + name + s[i+n:]
        i += n ; count += 1
        # g.es('.',newline=False)
        if 1:
            if not printFlag:
                printFlag = True
                # print p.h
            print g.get_line(s,i-n)
#@+node:ekr.20051110105027.164: *5* << print before and after >>
print "-"*10,count,p.h
print "before..."
print p.b
print "-"*10,"after..."
print s
#@+node:ekr.20051110105027.165: *4* << set nameList to the list of functions in leoGlobals.py >>
nameList = (
'alert',
'angleBrackets',
'appendToList',
'callerName',
'CheckVersion',
'choose',
'clearAllIvars',
'clear_stats',
'collectGarbage',
'computeLeadingWhitespace',
'computeWidth',
'computeWindowTitle',
'createTopologyList',
'create_temp_name',
'disableIdleTimeHook',
'doHook',
'dump',
'ecnl',
'ecnls',
'enableIdleTimeHook',
'enl',
'ensure_extension',
'es',
'esDiffTime',
'es_error',
'es_event_exception',
'es_exception',
'escaped',
'executeScript',
'file_date',
'findNodeAnywhere',
'findTopLevelNode',
'findNodeInTree',
'findReference',
'find_line_start',
'find_on_line',
'flattenList',
'funcToMethod',
'getBaseDirectory',
'getOutputNewline',
'getTime',
'get_Sherlock_args',
'get_directives_dict',
'get_leading_ws',
'get_line',
'get_line_after',
'getpreferredencoding',
'idleTimeHookHandler',
# 'importFromPath',
'initScriptFind',
'init_sherlock',
'init_trace',
'isUnicode',
'isValidEncoding',
'is_c_id',
'is_nl',
'is_special',
'is_ws',
'is_ws_or_nl',
'joinLines',
'listToString',
'makeAllNonExistentDirectories',
'makeDict',
'match',
'match_c_word',
'match_ignoring_case',
'match_word',
'module_date',
'openWithFileName',
'optimizeLeadingWhitespace',
'os_path_abspath',
'os_path_basename',
'os_path_dirname',
'os_path_exists',
'os_path_getmtime',
'os_path_isabs',
'os_path_isdir',
'os_path_isfile',
'os_path_join',
'os_path_norm',
'os_path_normcase',
'os_path_normpath',
'os_path_split',
'os_path_splitext',
'pause',
'plugin_date',
'plugin_signon',
'printDiffTime',
'printGc',
'printGcRefs',
'printGlobals',
'printLeoModules',
'print_bindings',
'print_stats',
'readlineForceUnixNewline',
'redirectStderr',
'redirectStdout',
'removeLeadingWhitespace',
'removeTrailingWs',
'reportBadChars',
'restoreStderr',
'restoreStdout',
'sanitize_filename',
'scanAtEncodingDirective',
'scanAtFileOptions',
'scanAtLineendingDirective',
'scanAtPagewidthDirective',
'scanAtRootOptions',
'scanAtTabwidthDirective',
'scanDirectives',
'scanError',
'scanf',
'set_delims_from_language',
'set_delims_from_string',
'set_language',
'shortFileName',
'skip_blank_lines',
'skip_block_comment',
'skip_braces',
'skip_c_id',
'skip_heredoc_string',
'skip_leading_ws',
'skip_leading_ws_with_indent',
'skip_line',
'skip_long',
'skip_matching_delims',
'skip_nl',
'skip_non_ws',
'skip_parens',
'skip_pascal_begin_end',
'skip_pascal_block_comment',
'skip_pascal_braces',
'skip_pascal_string',
'skip_php_braces',
'skip_pp_directive',
'skip_pp_if',
'skip_pp_part',
'skip_python_string',
'skip_string',
'skip_to_char',
'skip_to_end_of_line',
'skip_to_semicolon',
'skip_typedef',
'skip_ws',
'skip_ws_and_nl',
'splitLines',
'stat',
'stdErrIsRedirected',
'stdOutIsRedirected',
'toEncodedString',
'toUnicode',
'toUnicodeFileEncoding',
'top',
'trace',
'trace_tag',
'update_file_if_changed',
'utils_rename',
'windows',
'wrap_lines')
#@+node:ekr.20051110105027.150: *3* Find cr/lf in a directory
import fnmatch, os

def findDosFile(pattern, dirname):

    """Check for crlf in files"""

    files = os.listdir(dirname)
    names = fnmatch.filter(files, pattern)
    for name in names:
        path = g.os_path_join(dirname, name)
        if g.os_path_isfile(path):
            bytes = open(path, 'rb').read()
            count = bytes.count('\r\n')
            if '\0' not in bytes and count:
                print "%4d %s" % (count,path)

dir = "c:\prog\leoCvs\leo"
print ; findDosFile("*",dir)
#@+node:ekr.20070124092048: *3* Find entries in k.guiBindNamesDict
# A script to find uses of the names defined in k.guiBindNamesDict in Leo's core.

k = c.k ; d = k.guiBindNamesDict
keys = [z for z in d.values() if z not in k.tkNamesList]
keys.sort()
h = ' tkKeys.defineSpecialKeys'
for key in keys:
    for p in c.all_positions():
        if p.h != h and p.h.find('keywords') == -1:
            s = p.b
            for z in ('"%s"' % (key), "'%s'" % (key)):
                if s.find(z) != -1:
                    print '%20s %s' % (z,p.h)
#@+node:ekr.20060509121738.1: *3* Find longest body text
# Used for testing new colorizer.

pmax = p.copy()
n = len(p.b)

for p in p.self_and_subtree():
    if len(p.b) > n:
        n = len(p.b)
        pmax = p.copy()

c.selectPosition(pmax)
c.redraw()
#@+node:ekr.20060509121738.2: *3* Find most colorizer tags
# For testing the new colorizer.

def tags(p):
    c.selectPosition(p)
    w = c.frame.body.bodyCtrl
    names = w.tag_names()
    total = 0
    for name in names:
        theList = w.tag_ranges(name)
        if theList:
            print name,w.tag_ranges(name)
            total += len(theList)
    return total

pmax = p.copy()
n = tags(p) # len(p.b)

for p in p.self_and_subtree():
    # if len(p.b) > n:
    n2 = tags(p)
    if n2 > n:
        n = n2
        pmax = p.copy()

c.selectPosition(pmax)
c.redraw()
#@+node:ekr.20070213074001.1: *3* Find w.xxx
import string
import leoTkinterFrame
words = {}
word_chars = string.ascii_letters + string.digits + '_'
p = g.findTopLevelNode(c,'Code')
p1 = p.copy()
baseClass = leoTkinterFrame.leoTkTextWidget
allMatches = True
seen = {}
for p in p.self_and_subtree():
    if seen.get(p.v): continue
    seen[p.v] = True
    s = p.b
    i = 0
    while 1:
        j = s.find('w.',i)
        if j == -1: break
        ch = s[j-1]
        if j == 0 or ch not in word_chars:
            j += 2
            k = g.skip_c_id(s,j)
            word = s[j:k]
            if allMatches or not hasattr(baseClass,word):
                words[word] = 1 + words.get(word,0)
            i = k
        else:
            i += 2
keys = words.keys()
keys.sort()
aList = ['%3d %s' % (words.get(key),str(key)) for key in keys]
print g.listToString(aList)
g.es('searched %s' % p1.h)
#@+node:ekr.20161023110345.1: *3* For #325: Simplify the organization of commands
# https://github.com/leo-editor/leo-editor/issues/325
Simplify the organization of commands
# This issue has been closed and abandoned.
#@+node:ekr.20161023051437.1: *4* Script: set headlines to command names
'''
For each node p in commanderCommands.py, search p for @cmd decorators,
replacing the headline with command names.
'''
g.cls()
import re
root = g.findNodeAnywhere(c, '@file ../commands/commanderCommands.py')
pattern = re.compile(r'@cmd\((.*)\)') # [\'\"]
for p in root.self_and_subtree():
    # print('%4s %s' % (len(p.b), p.h))
    matches = list(pattern.finditer(p.b))
    if matches:
        # print('%25s %s' % (m.group(1), p.h))
        p.h = ' & '.join([m.group(1) for m in matches])
        print(p.h)
print('done')
#@+node:ekr.20161023051453.1: *4* Script: inject functions into commander
'''
For each node p in commanderCommands.py, insert the line:

    commander.def_name = def_name
    
after all function/method definition
'''
g.cls()
import re
root = g.findNodeAnywhere(c, '@file ../commands/commanderCommands.py')
assert root
pattern = re.compile(r'\bdef(\s)+(\w+)')
for p in root.self_and_subtree():
    # print(p.h)
    lines = []
    for m in pattern.finditer(p.b):
        name = m.group(2)
        lines.append('commander.%s = %s\n' % (name, name))
    if lines:
        print(''.join(lines))
        p.b = '%s\n\n%s' % (p.b.rstrip(), ''.join(lines))
print('done')
#@+node:ekr.20161023044018.1: *4* Script: find all calls to commander methods
'''Find all calls to commander methods in the present file.'''
g.cls()
import re
pattern = re.compile(r'c[12]*\.(\w+)\s*\(.*\)')
lines = set()
names = set()
for p in c.all_unique_nodes():
    for m in pattern.finditer(p.b):
        lines.add(m.group(0))
        names.add(m.group(1))
        # print('%30s %s' % (m.group(0), p.h))
for line in sorted(lines):
    print(line)
for name in sorted(names):
    print(name)
#@+node:ekr.20161023110341.1: *4* Script: find all external calls to commander
'''Find all calls to commander methods in the present file.'''
g.cls()
import re
pattern = re.compile(r'\bc[12]*\.(\w+)\s*\(.*\)')
table = (
    '@file leoCommands.py',
    '@file ../commands/commanderCommands.py',
    '@file ../external/codewise.py',
        # Uses c in non-standard ways.
)
exclude = set() # Don't include commands defined in commanderCommands.py
seen = set()
lines = set()
names = set()
p = g.findTopLevelNode(c, 'Code')
assert p
while p:
    if p.h in table or p.h in seen:
        if p.h not in seen:
            seen.add(p.h)
            print('Skipping %s' % p.h)
        p.moveToNodeAfterTree()
    else:
        if p.isAnyAtFileNode():
            seen.add(p.h)
            # print('%s' % p.h)
        for m in pattern.finditer(p.b):
            lines.add(m.group(0))
            names.add(m.group(1))
            # print('%30s %s' % (p.h[:30], m.group(0)))
        p.moveToThreadNext()
# commanderCommands.py should contain only commands, and *local* helpers.
p = g.findNodeAnywhere(c, '@file ../commands/commanderCommands.py')
assert p
pattern = re.compile(r'\bdef\s+(\w+)\s*\(') # (.*\):')
print('exclusions...')
for p in p.subtree():
    for m in pattern.finditer(p.b):
        exclude.add(m.group(1))
        # print('%40s %s' % (m.group(0)[:40], p.h))
        # print(m.group(1))
if 0:
    print('\nexclude...')
    for z in sorted(exclude):
        print(z)
if 0:
    print('\nlines...')
    for z in sorted(lines):
        print(z)
if 1:
    print('\nnames...')
    for z in sorted(names - exclude):
        print(z)
#@+node:ekr.20160428073540.1: *3* How long does it take to search LeoPy.leo?
@language python
# 7559 nodes: 0.075 sec.
import time
t1 = time.clock()
n = 0
for p in c.all_unique_positions():
    n += 1
    if p.h.startswith('@chapter '):
        pass
t2 = time.clock()
g.es('%s nodes, %5.3f sec' % (n, t2-t1))
#@+node:ekr.20111017085134.16018: *3* Inspect modules
import inspect

fn = g.os_path_finalize_join(g.app.loadDir,'leoNodes.py')

m = __import__ ('leoNodes')
# print(m)

classes = inspect.getmembers(m,inspect.isclass)
# print(classes)
print('='*20)
for z in classes:
    name,value = z
    print(name)
    members = inspect.getmembers(value)
    print('members of',name)
    for name2,value2 in members:
        if False: # not name2.startswith('__'):
            print('  ',name2)
        if name2 == '__init__':
            print('__init__',value2)
            if inspect.isfunction(value2):
                init_members = inspect.getmembers(value2)
                print('init members')
                for name3,value3 in init_members:
                    if not name3.startswith('__'):
                        print('    ',name3)
#@+node:ekr.20051216152812: *3* Obsolete: Insert begin/endUpdate
# Leo no longer uses begin/endUpdate.
u = c.undoer
w = c.frame.body.bodyCtrl
s1 = '''\
    c.beginUpdate()
    try:'''
s2 = '''\
    finally:
        c.endUpdate()'''

b = u.beforeChangeNodeContents(p)
i, j = g.app.gui.getSelectionRange(w)
if i != j:
    s = w.get(i,j)
    s = ''.join(['\t'+line for line in g.splitLines(s)])
    w.delete(i,j)
    w.insert(i,s1+'\n'+s+'\n'+s2)
else:
    w.insert(i,s1+'\n\t\t\n'+s2)
u.afterChangeNodeContents(p,'add-begin/endUpdate',b)
#@+node:ekr.20050704172623: *3* Print all headlines, properly indented
for p in c.all_positions():
    print p.level()*' ',p.h
#@+node:ekr.20040915080419: *3* Print all uAs (unknown attributes)
for p in c.all_positions():
    h = p.h
    if hasattr(p.v,'unknownAttributes'):
        print('v',h,p.v.unknownAttributes)
#@+node:EKR.20040517074600.13: *3* Print default font
font = g.app.config.defaultFont

print font.cget("family"), font.cget("weight")
#@+node:ekr.20131030082936.19134: *3* Print functions defined in leoGlobals.py
print("Names defined in leoGlobals.py",color="purple")
for name in sorted(g.__dict__.keys()):
    print(name)
#@+node:ekr.20130810093044.16954: *3* Print global data structures from in modes/*.py files
'''Create global data structures from modes/*.py files.'''

import glob
import imp

g.cls()

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)
known_keys = list(g.app.language_delims_dict.keys())
new_languages = {}

for z in aList:
    name = g.os_path_basename(z)
    name2 = name[:-3]
    if name2 in known_keys or name2.startswith('__'):
        if 0: print('ignore: %s' % (name2))
    else:
        try:
            theFile, pathname, description = imp.find_module(name2,[theDir])
            m = imp.load_module(name2, theFile, pathname, description)
            if hasattr(m,'properties'):
                # new_languages.append(name2)
                new_languages[name2] = m
            else:
                print('no properties: %s %s' % (name2,m))
        except Exception:
            g.es_exception()
            
print('%s new languages\n' % (len(list(new_languages.keys()))))
    
for key in sorted(new_languages.keys()):
    m = new_languages.get(key)
    aList2 = [m.properties.get(z)
        for z in ('lineComment','commentStart','commentEnd')
            if m.properties.get(z)]
    print('%-20s : "%s",' % (
        '"%s"' % (key),
        ' '.join(aList2)))
    # computed[name2] = ' '.join(aList2)
       
if 0:
    mismatches = 0
    print()
    for z in known_keys:
        val = g.app.language_delims_dict.get(z)
        val2 = computed.get(z)
        if not val:
            print('no val',z)
        elif not val2:
            print('no val2',z)
        elif val != val2:
            mismatches += 1
            print('mismatch for %s. expected %s got %s' % (z,repr(val),repr(val2)))
            print(repr(val))
            print(repr(val2))
    print('%s mismatches' % mismatches)
#@+node:ekr.20040717121014: *3* Print gnx
print "gnx", p.v.fileIndex, p.h
#@+node:ekr.20141105055521.15: *3* Print gnxs & gnxDict
@language python
'''A script used while investigating this bug.'''
# g.cls()
d = {}
x = g.app.nodeIndices
result = []
for v in c.all_unique_nodes():
    gnx = v.fileIndex
    assert g.isUnicode(gnx),gnx
    d [gnx] = v
    result.append('%s %s' % (gnx,v))
print('%s v.fileIndex\'s...' % len(result))
print('\n'.join(sorted(result)))
if 1:
    d = c.fileCommands.gnxDict
    print('old: %s fc.gnxDict keys...' % len(list(d.keys())))
    for key in sorted(d.keys()): 
        print('%s %s' % (key,d.get(key)))
if 1:
    c.recreateGnxDict()
    d = c.fileCommands.gnxDict
    print('new: %s fc.gnxDict keys...' % len(list(d.keys())))
    for key in sorted(d.keys()): 
        print('%s %s' % (key,d.get(key)))
#@+node:ekr.20111017085134.16022: *3* Print long lines
# This works, but splitting long lines by hand
# is the very essence of stupidity.

# Don't use this script!
# Use a *reliable* pep8 tool instead.

g.es('finding long lines in',p.h)
found = False
while p and not found:
    for line in g.splitLines(p.b):
        if len(line) > 80:
            found = True
            g.es('long line in',p.h)
            c.selectPosition(p)
            break
    else:
        p.moveToThreadNext()
g.es('done')
#@+node:ekr.20111017085134.16020: *3* Print max nesting level
n = 0

for p in c.all_positions():
    n = max(n,p.level())

g.es('n',n)

last = 0
delta = 0
d = {}

for p in c.all_positions():
    n = p.level()
    if n < last:
        delta = max(delta,last-n)
        d [last-n] = d.get(last-n,0) + 1
    last = n

g.es('delta',delta)
g.es('d',d)
#@+node:EKR.20040613162717: *3* Print newline stats
path = g.os_path_join(g.app.loadDir,"leo.py")

try:
    f = open(path,"rb")
    s = f.read()
    f.close()
    cr = 0 ; nl = 0
    for ch in s:
        if ch == '\r': cr += 1
        if ch == '\n': nl += 1
    m = "cr %d, nl %d %s" % (cr,nl,path)
    print m ; g.es(m)
except IOError:
    print "can not open",path
#@+node:ekr.20170120110948.4: *3* Print Qt color names
# This script prints the list of known Qt names. Qt seems to ignore case.
from leo.core.leoQt import QtGui
aList = sorted([g.u(z) for z in QtGui.QColor().colorNames()])
print('\n'.join(aList))
#@+node:ekr.20051110105027.151: *3* Print statistics using dis module
# routines to gather static statistics about opcodes based on dis module.
import compiler
import dis
import os
import string
import sys
import types

@others
#@+node:ekr.20051110105027.152: *4* go
def go():

    dir = "c:/prog/leoCVS/leo/"
    modules = getModules(dir)
    stats = [0] * 256
    try:
        # Importing these might start leo itself and hang idle.
        modules.remove("leo")
        modules.remove("openLeo")
        modules.remove("openEkr")
        modules.remove("setup")
    except: pass
    # print modules

    for m in modules:
        try:
            print "module:", m
            exec("import " + m)
            a = eval(m)
            any(a,stats)
        except:
            import traceback ; traceback.print_exc()
            print "----- no matching class in", m

    g.print_stats(stats)
#@+node:ekr.20051110105027.153: *4* getFiles
def getFiles (dir):

    # Generate the list of modules.
    allFiles = os.listdir(dir)
    files = []
    for f in allFiles:
        head,tail = g.os_path_split(f)
        root,ext = g.os_path_splitext(tail)
        if ext==".py":
            files.append(g.os_path_join(dir,f))

    return files
#@+node:ekr.20051110105027.154: *4* getModules
def getModules (dir):

    """Return the list of Python files in dir."""

    files = []

    try:
        allFiles = os.listdir(dir)
        for f in allFiles:
            head,tail = g.os_path_split(f)
            fn,ext = g.os_path_splitext(tail)
            if ext==".py":
                files.append(fn)
    except: pass

    return files
#@+node:ekr.20051110105027.155: *4* any
def any(x,stats,printName = 0):
    # based on dis.dis()
    """Gathers statistics for classes, methods, functions, or code."""
    if not x:
        return
    if type(x) is types.InstanceType:
        x = x.__class__
    if hasattr(x, 'im_func'):
        x = x.im_func
    if hasattr(x, 'func_code'):
        x = x.func_code
    if hasattr(x, '__dict__'):
        items = x.__dict__.items()
        items.sort()
        for name, x1 in items:
            if type(x1) in (types.MethodType,
                            types.FunctionType,
                            types.CodeType):
                if printName: print name
                try:
                    any(x1,stats)
                except TypeError, msg:
                    print "Sorry:", msg
    elif hasattr(x, 'co_code'):
        code(x,stats)
    else:
        raise TypeError, \
              "don't know how to disassemble %s objects" % \
              type(x).__name__
#@+node:ekr.20051110105027.156: *4* code
def code (co, stats):
    """Gather static count statistics for a code object."""

    codeList = co.co_code
    # Count the number of occurances of each opcode.
    i = 0 ;  n = len(codeList)
    while i < n:
        c = codeList[i]
        op = ord(c)
        stats[op] += 1
        i = i+1
        if op >= dis.HAVE_ARGUMENT:
            i = i+2
#@+node:ekr.20051110105027.157: *4* print_stats
def print_stats (stats):

    stats2 = [] ; total = 0
    for i in xrange(0,256):
        if stats[i] > 0:
            stats2.append((stats[i],i))
        total += stats[i]

    stats2.sort()
    stats2.reverse()
    for stat,i in stats2:
        print string.rjust(repr(stat),6), dis.opname[i]
    print "total", total
#@+node:ekr.20041124144944: *3* Print sys.path
import os
import sys
for s in sys.path:
    exists = os.path.exists(s)
    print "%5s %s" % (exists,s)
#@+node:ekr.20040322120331: *3* Print tnodeList's
print '-'*20
for p in c.all_positions():
    if hasattr(p.v,"tnodeList"):
        print p,p.v.tnodeList
#@+node:ekr.20150415144952.1: *3* Print whether focus widget supports high-level interface
'''Determines whether the focus widget supports the high-level interface.'''
g.cls()
import leo.plugins.qtGui as qtGui
import leo.core.leoFrame as leoFrame
import PyQt4.QtGui as QtGui
if 0:
    c.logWantsFocusNow()
elif 1: # Edit a headline
    c.editHeadline(p)
    # c.redraw()
body_w = c.frame.body.bodyCtrl.widget
w = QtGui.QApplication.focusWidget()
print('Focus','isBodyCtrl',w == body_w,w)
tree = c.frame.tree
while w:
    # isText = g.app.gui.isTextWidget(w)
    if isinstance(w,QtGui.QLineEdit):
        wrapper = tree.getWrapper(w,item=None)
        if isinstance(wrapper,qtGui.leoQtBaseTextWidget):
            print('QLineEdit has wrapper',w,wrapper)
        else:
            print('QLineEdit: NO wrapper',w)
            # wrapper = tree.headlineWrapper(c,item=None,name='find-head-wrapper',widget=w)
            # print('QLineEdit NEW wrapper',w,wrapper)
        break
    elif isinstance(w,QtGui.QTextEdit):
        wrapper = getattr(w,'leo_wrapper',None)
        if wrapper:
            if isinstance(wrapper,qtGui.leoQtBaseTextWidget):
                print('QTextEdit has text wrapper',w,wrapper)
            elif isinstance(wrapper,qtGui.leoQtLog):
                logCtrl = wrapper.widget # same as wrapper.logCtrl
                print('QtTextEdit has log wrapper',w,logCtrl)
            else:
                print('unknown wrapper',wrapper)
        else:
            wrapper = qtGui.leoQTextEditWidget(w,'find-wrapper',c=c)
            print('QTextEdit NEW wrapper',w,wrapper)
        break
    print('Fail',w)
    wrapper = False
    if hasattr(w,'parent'):
        w = w.parent()
    else:
        print('no parent',w)
        break
if wrapper:
    print('is searchable (has wrapper)',w,wrapper)
    # Make sure wrapper supports the high-level interface.
    print('high level?',isinstance(wrapper,leoFrame.HighLevelInterface))
    table = (
        'insert',
        'getAllText',
        'setAllText',
        'setInsertPoint',
        'setSelectionRange',
    )
    for ivar in table:
        print(bool(getattr(wrapper,ivar,None)),ivar)
#@+node:ville.20090508224531.9800: *3* Prototype: pos_to_archive, archive_to_pos
def pos_to_archive(p):
    """ Archived version of position in format gnx1:gnx2:gnx3 

    This is more tolerant to tree modification than children index version 
    given by p.archivedPosition()

    Counterpart of archive_to_pos
    """

    ps = [po.gnx for po in p.self_and_parents()]
    ps.reverse()
    return ":".join(ps)

def sibling_by_gnx(p, gnx):
    """ Look through siblings of p for a node with specified gnx """
    for po in p.siblings():
        #g.es('test' , po, po.gnx)
        if po.gnx == gnx:
            return po
    raise IndexError("Pos %s has no sibling gnx = %s" % (p,gnx))            

def archive_to_pos(c,s):
    """ Convert gnx1:gnx2:gnx3 string representation of position to a real position 

    Counterpart of pos_to_archive.

    """

    ps = s.split(':')

    cur = c.rootPosition()
    for gnx in ps[:-1]:
        ne = sibling_by_gnx(cur, gnx)
        cur = ne.firstChild()

    return sibling_by_gnx(cur, ps[-1]).copy()

arc= pos_to_archive(p)
po = archive_to_pos(c, arc)

assert po == p
#@+node:ekr.20041126055818.2: *3* Prototype: pyclbr module (parses python)
@ This is probably a better way of parsing Python text.
It does not import the module, so it is safe for untrusted code.
@c
import pyclbr # Python Command Line Browser support.
import sys
print '*' * 40 ; print
fileNames = ("leoCommands.py","leo.py","leoAtFile.py")
fileNames = (r"c:\Python23\Lib\site-packages\Pmw\Pmw_1_1\lib\PmwPanedWidget.py"),

for fileName in fileNames:
    dir,file = g.os_path_split(fileName)
    moduleName,ext = g.os_path_splitext(file)
    moduleDict = pyclbr.readmodule_ex(moduleName,[dir] + sys.path)
    print "module", moduleName,'-' * 40
    items = []
    for funcOrClass in moduleDict.keys():
        o = moduleDict.get(funcOrClass) # o is a descriptor.
        try:
            mdict = o.methods # Fails for functions.
            items.append((int(o.lineno),"*class",o.name),)
            mkeys = mdict.keys()
            for method in mkeys:
                lineno = mdict.get(method)
                items.append((int(lineno),"method",method),)
        except AttributeError:
            # funcOrClass is a function descriptor
            items.append((int(o.lineno),"function",o.name),)
    items.sort()
    for line,kind,name in items:
        print "%4d %8s %s" % (line,kind,name)
#@+node:ekr.20041019080125: *3* Report loaded plugins
print "Loaded plugins..."

for s in g.app.loadedPlugins:
    print s
#@+node:ekr.20041013101029: *3* Rexex find in headline
# Run this script from a scriptButton.
<< about this script >>
import re

def headfind():
    """Search with re and 
    - GO to found headline beginning with the selected text or clipboard buffer
    or also GO when line begins with @ and word or string in variable sMyOwnPrefix
    - EXCEPT when found search string is '-info' node
    (BOTH  1.followed by ' -info'
    AND  2.appears anywhere in headline(preceding space or start) 
    THEN  just SHOW found info node's body text in cleared Log pane.
    """
    s = c.frame.body.getSelectedText() or g.app.gui.getTextFromClipboard()
    if s:
        if len(s) == 1: s = "index -info" #if select is one char try to goto this named index node
        s = re.escape(s.lower())
        sUseLogTrigger = re.escape(" -info")
        sMyOwnPrefix = re.escape("FOLLOWING FILE IS: ").lower()
        sAllowablePrefixRe = "\@([A-Za-z][A-Za-z0-9\-]+) "
            # @ char, followed by alpha,some alphanum or dash chars, then space ...matches Leo special nodes
        for p in c.all_positions():
            srch="(^%s%s|^%s%s|^%s| %s%s)" % (sMyOwnPrefix,s,sAllowablePrefixRe,s,s,s,sUseLogTrigger) #all re
            if re.findall(srch,p.h.lower()):
                g.es("found " + s)
                sUseLogTrigger_srch="(^| )%s%s" % (s,sUseLogTrigger) #first just Log trigger re
                if re.findall(sUseLogTrigger_srch,p.h.lower()):
                    body2=p.b
                    c.frame.log.logCtrl.delete("1.0","end"); # clear Log pane before message
                    # g.es(body2,color="orange")
                    return
                else:
                    c.frame.tree.expandAllAncestors(p)
                    c.selectVnode(p)
                    c.redraw()
                    return

        g.es("no headline matches '%s'" % (s),color="blue")
    else:
        g.es("no selected text & clipboard empty",color="blue")

headfind()
#@+node:ekr.20041013101029.1: *4* << about this script >>
@ PREFIXES: Now will jump to any headline where search is preceded by an @+chars+space
and alternatively a fixed prefix string+space.

CLEAR LOG: Now also clears Log for display of -info nodes. 

NOTE:I already had a file with a bunch of text files each preceded by "THE FOLLOWING
FILE IS: " and a list of these files at the top of everything. After global
changing these lines with "- " (and at first line), I imported flattened outline...
and "there you go" a index-driven Leo version. :)

bill p
#@+node:ekr.20130802103517.20482: *3* Show Call hierarchy w/ trace module
@language python
@tabwidth -4

"""
Create a Leo suboutline containing summary of a previous trace session.

Run (ctrl+b) this script after::

    cd ~/leo-editor
    python -m trace --trackcalls launchLeo.py --gui=qt >trace.txt

"""
from __future__ import print_function
import os
g.cls()
f = g.os_path_finalize_join(g.app.loadDir,'..','..','trace.txt')
tr = open(os.path.expanduser(f))
top = p.insertAfter()
top.h = 'trace session'
cur,no,n = None,None,0
for l in tr:
    if l.startswith('***'):
        cur = top.insertAsLastChild().copy()
        cur.h = os.path.basename(l.split()[1])
    elif l.startswith('  -->'):
        no = cur.insertAsLastChild().copy()
        no.h = os.path.basename(l.split()[1].strip())
    elif no:
        no.b += l.strip() + '\n'
    n += 1
    if (n % 100) == 0:
        print(".",end='')
c.redraw()
#@+node:EKR.20040517074600.10: *3* Show font
body = c.frame.body.bodyCtrl

font = c.config.getFontFromParams(
        "body_text_font_family", "body_text_font_size",
        "body_text_font_slant",  "body_text_font_weight",
        tag = "body")

print(font)
print(body)

# body.configure(font=font)
#@+node:EKR.20040517074600.11: *3* Show settings
import tkFont

@others

# Body pane.
fn = c.frame.body.cget("font")
font = tkFont.Font(font=fn)
name,size,slant,weight = getFontSettings(font)
print "body:",fn,name,size,slant,weight

if 0:

    # Log pane.
    fn = c.frame.log.getFontConfig()
    font = tkFont.Font(font=fn)
    name,size,slant,weight = getFontSettings(font)
    g.es("log:" + name + "," + `size` + "," + slant + "," + weight)

    # Tree pane.
    font = c.frame.tree.getFont()
    name,size,slant,weight = getFontSettings(font)
    g.es("head:" + name + "," + `size` + "," + slant + "," + weight)
#@+node:EKR.20040517074600.12: *4* getFontSettings
def getFontSettings (font):

    name   = font.cget("family")
    size   = font.cget("size")
    slant  = font.cget("slant")
    weight = font.cget("weight")

    return name, size, slant, weight
#@+node:ekr.20040715105834: ** Fun...
#@+node:ekr.20050803075926: *3* sudoku puzzle
@tabwidth -4

# Solves the sudoku puzzle.
# For another Leo program that does this, see: http://members.dslextreme.com/users/kayvan/sudoku/

import copy

digits = '123456789' # valid digits.

if 0:
    << 1-star puzzles >>
    << 2-star puzzles >>
    << 3-star puzzles >>
    << 4-star puzzles >>
    << 5-star puzzles >>

<< define data >>

@others

print '-' * 40

solver = sudokuClass(data=data)
if not solver.errors:
    solver.solve()
#@+node:ekr.20050804073824: *4* << define data >>
data = (
    '',
) 

data = (     # 9/2107 5 stars
    '7xx1xx6xx',
    'xxxxx6x14',
    '1xxx5xxx2',
    'x3x4xx82x',
    'xxx7x2xxx',
    'x71xx9x3x',
    '8xxx2xxx3',
    '31x8xxxxx',
    'xx7xx5xx8',
)
#@+node:ekr.20050911123109: *4* << 5-star puzzles >>
# Neither of these are solvable without guessing.

data = ( 
    'xx24xxxxx',
    'x41x3xxxx',
    '8xxx6xx4x',
    'x6xxx3xx9',
    'x7x9x8x3x',
    '2xx6xxx7x',
    'x2xx4xxx1',
    'xxxx1x72x',
    'xxxxx64xx') # 4/27/07 5 stars (very hard)

data = ( 
    '1x5xxx37x',
    'xxxxxx2xx',
    'x973xxx1x',
    'xxxx531x2',
    '3xx8x1xx4',
    '2x147xxxx',
    'x7xxx864x',
    'xx8xxxxxx',
    'x12xxx8x7') # 8/4 5 stars

data = ( 
    '2xxxx1834',
    'xxxx9xxxx',
    'x1x3xxx5x',
    'xx75xxxxx',
    '16xxxxx72',
    'xxxxx93xx',
    'x7xxx4x1x',
    'xxxx8xxxx',
    '8549xxxx3') # 8/9 5 stars (may be invalid)
#@+node:ekr.20050911145104: *4* << 4-star puzzles >>
data = (
    'x13xxxxx2',
    '6x2xx4xx8',
    '4xx3xxx6x',
    '2xx8xxxx7',
    'xxx715xxx',
    '9xxxx3xx4',
    'x2xxx1xx3',
    '1xx5xx4x9',
    '8xxxxx62x') # 9/10/05 4 stars
#@+node:ekr.20050811075608: *4* << 3-star puzzles >>
data = (
    '8xxx9x21x',
    'x9x4xxxxx',
    'xx58x7xx9',
    '7xx1xx9xx',
    'xxxx5xxxx',
    'xx6xx3x28',
    '6xx5x93xx',
    'xxxxx6x7x',
    'x48x1xxx6',
)

data = ( # 8/3: solvable.
    'x5xx9xxxx',
    'xx48xxxx9',
    'xxx1x728x',
    '56xxxx137',
    'xxxxxxxxx',
    '173xxxx42',
    'x215x8xxx',
    '6xxxx38xx',
    'xxxx1xx6x')

data = ( # 1 stars
    'x4xxxx179',
    'xx2xx8x54',
    'xx6xx5xx8',
    'x8xx7x91x',
    'x5xx9xx3x',
    'x1xx6xx4x',
    '3xx4xx7xx',
    '57x1xx2xx',
    '928xxxx6x')

data = (
    '6xx75x1xx',
    '8xxxx34xx',
    'x3x96xx25',
    'xxx4xx3x2',
    '7xxxxxxx6',
    '2x1xx5xxx',
    '31xx89x4x',
    'xx65xxxx1',
    'xx5x42xx3') # solvable.

data = (
    'xxxxx6xx5',
    'xx41xx8xx',
    'x5xx78x42',
    '58xxxx9xx',
    '3xxxxxxx7',
    'xx6xxxx18',
    '24x39xx7x',
    'xx7xx52xx',
    '9xx7xxxxx') # solvable

data = (
    'xxxxx6xx5',
    'xx41xx8xx',
    'x5xx78x42',
    '58xxxx9xx',
    '3xxxxxxx7',
    'xx6xxxx18',
    '24x39xx7x',
    'xx7xx52xx',
    '9xx7xxxxx') # 9/8/05 3 stars

data = (
    'xxxx64x15',
    'x549xx6x2',
    'xxxxxxx7x',
    'xxxx8x2xx',
    '1x8xxx5x7',
    'xx7x4xxxx',
    'x3xxxxxxx',
    '8x2xx319x',
    '94x87xxxx') # 9/9/05 3 stars

data = (
    '8xxxxxxxx',
    'x915x36xx',
    'x62xxxx8x',
    'xx9xx8xxx',
    'x752x984x',
    'xxx4xx9xx',
    'x1xxxx42x',
    'xx49x276x',
    'xxx7xxxx5',
)
#@+node:ekr.20050929065040: *4* << 2-star puzzles >>
data = (
    '4xxxxxxxx',
    '96xxx85xx',
    'x374x6xx1',
    '3x48xxx6x',
    'xxxx1xxxx',
    'x5xxx92x7',
    '5xx1x267x',
    'xx95xxx82',
    'xxxxxxxx9',
)

data = (
    'xxx395xxx',
    'xx5xx89x2',
    'xxxx2xxx5',
    '6x2xxxxx7',
    'x84xxx53x',
    '7xxxxx1x6',
    '3xxx6xxxx',
    '5x62xx7xx',
    'xxx831xxx',
)

data = ( # 8/6 2 stars
    '2x6xxxx49',
    'x37xx9xxx',
    '1xx7xxxx6',
    'xxx58x9xx',
    '7x5xxx8x4',
    'xx9x62xxx',
    '9xxxx4xx1',
    'xxx3xx49x',
    '41xxxx2x8')

data = (
    '9xx7x3xx6',
    'x87xx2xxx',
    '15xxxxx9x',
    'xxx6xx82x',
    'xx8xxx1xx',
    'x26xx8xxx',
    'x6xxxxx31',
    'xxx4xx97x',
    '4xx2x1xx8') # 8/30 2 stars

data = (
    '8xx3xxx7x',
    'xx57xxxxx',
    '9xx165x3x',
    '34xxxxxx9',
    'xxx5x4xxx',
    '7xxxxxx83',
    'x8x253xx4',
    'xxxxx65xx',
    'x2xxx1xx6',
) # 9/6/ 2 stars

data = ( 
    'x6x29xx8x',
    'xx8xxxx3x',
    'x1xx78xxx',
    'x217x9xx8',
    '6xxxxxxx3',
    '7xx6x492x',
    'xxx12xx4x',
    'x7xxxx3xx',
    'x5xx86x7x') # 2 stars
#@+node:ekr.20050927122648: *4* << 1-star puzzles >>
data = (
    'x4735xxx9',
    'x5x8x93xx',
    'xx84xx12x',
    '12x57xxx8',
    '7x5xxx2x6',
    '3xxx82x17',
    'x12xx59xx',
    'xx92x6x4x',
    '4xxx1875x',
)

data = (
    '19xxx84xx',
    '7xxx9xxxx',
    'x5xxxx986',
    'x19xxxx6x',
    '8xxxxxxx5',
    'x6xxxx72x',
    '684xxxx3x',
    'xxxx2xxx7',
    'xx26xxx14',
)

data = (
    'xx2xx7xx9',
    'x8x249x3x',
    'x31xx572x',
    'xx9xx8xx1',
    'x65xxx847',
    '4xx7xx2xx',
    'x931xx65x',
    'x5x862x73',
    '8xx5xx4xx') # 1 star

data = ( # 8/10 1 star
    'x6xxx5x19',
    'x9x34xxxx',
    'x8x96x5xx',
    'xxx8x93x1',
    '8x9xxx4x7',
    '2x17x4xxx',
    'xx3x86x4x',
    'xxxx27x6x',
    '47x5xxx8x')
#@+node:ekr.20050803075926.1: *4* class sudokuClass
class sudokuClass:

    '''A class to solve the sudoku puzzle.'''

    @others
#@+node:ekr.20050803075926.2: *5*  ctor (main) & helpers
def __init__ (self,data):

    # g.trace('main')
    self.cells = []
    self.cols = []
    self.colGroups = []
    self.data = data
    self.errors = 0
    self.excludedGroupNumbers = []
    self.level = 0
    self.rowGroups = []
    self.rows = []
    self.squareGroups = []
    self.tracing = True
    self.valid = True
    self.verbose = False

    # Check the data and finish the init process.
    self.checkData()
    self.initFromData()
    self.printData()
    self.finishInit()
    # self.dump()
#@+node:ekr.20050803202932: *6* initFromData
def initFromData (self):

    i = 0
    for row in self.data:
        thisRow = []
        j = 0
        for ch in row:
            if ch in digits:    val = ch
            else:               val = None
            self.cells.append(self.cellClass(self,val,i,j))
            thisRow.append(val)
            j += 1
        self.rows.append(thisRow)
        i += 1

    for j in xrange(9):
        col = [row[j] for row in self.rows]
        self.cols.append(col)
#@+node:ekr.20050803075926.4: *6* finishInit
def finishInit (self):

    for i in xrange(9):
        self.squareGroups.append(self.squareGroupClass(self,i))
        self.rowGroups.append(self.colGroupClass(self,i))
        self.colGroups.append(self.rowGroupClass(self,i))

    for z in self.squareGroups:
        z.finishCreate()
    for z in self.rowGroups:
        z.finishCreate()
    for z in self.colGroups:
        z.finishCreate()

    # Must be done last!
    for z in self.squareGroups:
        z.computeRelatedGroups()
    for z in self.cells:
        z.finishCreate()
#@+node:ekr.20050803121102: *6* checkData
def checkData (self):

    rows = len(self.data)

    if rows != 9:
        return self.error('wrong number of rows: %d' % rows)

    for row in self.data:
        cols = len(row)
        if cols != 9:
            return self.error('wrong number of columns in row %d: %d' % (i,cols))
#@+node:ekr.20050804070733: *6* check & helper
def check (self):

    for groups in (self.colGroups,self.rowGroups,self.squareGroups):
        for group in groups:
            if not self.checkGroup(group):
                return False
    return True
#@+node:ekr.20050804071049: *7* checkGroup
def checkGroup (self,group):

    vals = []
    for cell in group.cells:
        n = len(cell.values)
        if n == 1:
            val = cell.values[0]
            if val in vals:
                g.trace('%s appears twice in group %s' % (val,repr(group)))
                return False
            vals.append(val)
    return True
#@+node:ekr.20050804071242: *5* printing & dumping
#@+node:ekr.20050803080858: *6* dump
def dump (self):

    if 0:
        print ; print 'groups...'
        for group in self.groups:
            print 'group %d, rowsNumbers: %s colNumbers: %s' % (
                group.groupNumber,group.rowNumbers,group.colNumbers)

    if 0:
        print ; print 'row groups...'
        for group in self.rowGroups:
            print '%d %s' % (group.rowNumber, group.rowcol)

    if 0:
        print ; print 'related groups...'
        for group in self.groups:     
            print 'Groups related to group %d: %s' % (
                group.groupNumber,[g.groupNumber for g in group.relatedGroups])
#@+node:ekr.20050803121730: *6* printData
def printData (self,tag='initial data'):

    print ; print tag ; print

    i = 0
    for row in self.rows:
        i += 1
        print
        for ch in row:
            if ch:
                print ('  %s  ') % ch,
            else:
                print ' ___ ',
        if i % 3 == 0 and i < 9:
            print ; print ; print '_' * 53
        print
    print
#@+node:ekr.20050803200132: *6* printCells
def printCells (self,tag=''):

    print
    if tag: print tag ; print

    i = 0
    for cell in self.cells:
        if len(cell.values) == 9:
            print '%7s' % '1..9',
        else:
            print '%7s' % ''.join(cell.values),
        i += 1
        if i % 9 == 0:
            print
#@+node:ekr.20050911112043.1: *5* Utils
#@+node:ekr.20050803095202: *6* groupNumber
def groupNumber (self,row,col):

    return (3 * (row // 3)) + (col // 3)
#@+node:ekr.20050803075926.3: *6* error
def error (self,s):

    print 'oops',s
    self.errors += 1
#@+node:ekr.20050803215553: *6* trace
def trace (self,s):

    if self.tracing:
        print s
#@+node:ekr.20050803202932.1: *6* isFinished
def isFinished (self):

    for cell in self.cells:
        n = len(cell.values)
        assert(n > 0) # We should have check for self.valid previously.
        if n > 1:
            return False
    return True
#@+node:ekr.20050911094859: *5* Guesses
#@+node:ekr.20050803203001: *6* findBestGroup
def findBestGroup (self,excludedGroupNumbers):

    bestGroup = None
    bestKnown = 0
    for group in self.squareGroups:
        n = 0 # Number of known cells
        for cell in group.cells:
            if len(cell.values) == 1:
                n += 1
        if 9 > n > bestKnown:
            if group.groupNumber not in excludedGroupNumbers:
                bestGroup = group
                bestKnown = n

    if bestGroup:
        if self.tracing:
            print ; print 'best group %d' % bestGroup.groupNumber
            if 0:
                if self.verbose:
                    print 'unknown cells',
                    for cell in bestGroup.unknownCells():
                        print cell,
                    print 'unknown vals',
                    for val in bestGroup.unknownVals():
                        print val,
                    print

    return bestGroup
#@+node:ekr.20050803210939: *6* findGuesses
def findGuesses (self):

    guesses = []
    group = self.findBestGroup(self.excludedGroupNumbers)
    self.excludedGroupNumbers.append(group.groupNumber)
    if not group:
        g.trace('No groups left to guess: %s' % excludedGroupNumbers)
        self.valid = False
        return []

    # Generate all combinations of cells and unkown vals.
    cells = [cell for cell in group.cells if len(cell.values) > 1]
    vals = []
    for cell in cells:
        for val in cell.values:
            if val not in vals: vals.append(val)
    n = len(vals)
    for i in xrange(n):
        guess = [] ; j = 0
        for cell in cells:
            bunch = g.bunch(cell=cell,val=vals[(i+j)%n])
            j += 1
            guess.append(bunch)
        if self.isValidGuess(guess):
            guesses.append(guess)

    if not guesses:
        g.trace('No valid guess for group %d' % group.groupNumber)
        self.valid = False
        return []

    if 0: # Another trace is in initFromGuess
        print 'level %d guesses...' % self.level
        for guess in guesses:
            for bunch in guess:
                print bunch.cell,bunch.val

    return guesses
#@+node:ekr.20050804060706: *6* isValidGuess
def isValidGuess (self,guess):

    return True ##

    for bunch in guess:
        if not bunch.cell.valIsPossible(bunch.val):
            return False

    return True
#@+node:ekr.20050803075926.5: *5* solve (main)
def solve (self):

    n = 0 ; self.valid = True
    while not self.errors and self.valid:
        n += 1
        self.progress = 0
        if self.tracing:
            print '*' * 40
            print 'solve: iteration %d at level %d' % (n,self.level)
        if not self.check(): return False
        if self.tracing: self.printCells()
        for cell in self.cells:
            # Reduce the possible values for the cell.
            cell.reduce()
        if not self.valid: break
        for cell in self.cells:
            # Find any values that appear only in one place in a group.
            cell.unique()
        if self.isFinished():
            self.printCells('success!') ; return True
        if self.tracing: self.printCells()
        for cell in self.cells:
            # Remove any possible values that would make other groups impossible.
            cell.removeConflicts()
        if not self.valid: break
        if self.isFinished():
            if self.level == 0: self.printCells('success!')
            return True
        if self.progress == 0:
            << guess an answer >>

    if self.tracing:
        if not self.valid:
            print ; print 'reached invalid state'
        if self.progress == 0:
            print ; print 'no progress'
        self.printCells()
    return False
#@+node:ekr.20050911085945: *6* << guess an answer >>
if self.level < 2:
    # Save the previous data.
    save = [g.bunch(cell=cell,values=cell.values[:]) for cell in self.cells]
    guesses = self.findGuesses()
    if self.tracing:
        print '-'*20,'%d valid guesses' % len(guesses)
    if not guesses:
        return False
    n = 0
    for guess in guesses:
        # Restore the previous state.
        for b in save:
            b.cell.values = b.values[:]
        # Make the guess.
        self.level += 1 ; n += 1
        if self.tracing:
            print ; print '-'*40,'making guess %d at level %d' % (n,self.level)
        for b in guess:
            b.cell.values = str(b.val)
            if self.tracing: g.trace(b.cell,b.val)
        if self.tracing:
            self.printCells()
        # Call ourselves recursively.
        ok = self.solve()
        self.level -= 1
        if ok: return True
    if self.tracing or self.level == 0:
        print 'no solution is possible at level %d' % self.level
    # Restore the previous state.
    for b in save:
        b.cell.values = b.values[:]
    return False
else:
    if self.tracing:
        print 'maximum depth exceeded'
return False
#@+node:ekr.20050911135016: *5* group classes
@ A group is essentially just a collection of cells.
#@+node:ekr.20050911101819: *6* class squareGroupClass
class squareGroupClass:

    @others
#@+node:ekr.20050803121102.2: *7*  ctor
def __init__ (self,sudoku,n):

    # g.trace('square',n)
    self.groupNumber = n
    self.main = sudoku

    # Set later...
    self.cells = []
    self.colNumbers = []
    self.rowNumbers = []
    self.relatedGroups = []
#@+node:ekr.20050911101819.1: *7* __repr__ & __str__
def __repr__ (self):

    return '<square group %d>' % self.groupNumber

__str__ = __repr__
#@+node:ekr.20050803130829: *7* finishCreate
def finishCreate (self):

    main = self.main

    self.cells = [cell for cell in main.cells if cell.groupNumber == self.groupNumber]

    for cell in self.cells:
        cell.squareGroup = self

    self.rowNumbers = []
    for cell in self.cells:
        if cell.i not in self.rowNumbers:
            self.rowNumbers.append(cell.i)
    self.rowNumbers.sort()

    self.colNumbers = []
    for cell in self.cells:
        if cell.j not in self.colNumbers:
            self.colNumbers.append(cell.j)
    self.colNumbers.sort()
#@+node:ekr.20050803161504: *7* computeRelatedGroups
def computeRelatedGroups (self):

    self.relatedGroups = []
    for group in self.main.squareGroups:
        if group is not self:
            related = False
            for n in group.colNumbers:
                if n in self.colNumbers:
                    related = True
            for n in group.rowNumbers:
                if n in self.rowNumbers:
                    related = True
            if related and group not in self.relatedGroups:
                self.relatedGroups.append(group)
#@+node:ekr.20050910194752: *6* class colGroupClass
class colGroupClass:

    @others
#@+node:ekr.20050910194752.1: *7* ctor
def __init__ (self,sudoku,j):

    # g.trace('col',j)
    self.j = j
    self.main = sudoku

    # Set later...
    self.cells = []
    self.col = None
#@+node:ekr.20050911102800: *7* __repr__ & __str__
def __repr__ (self):

    return '<col group %d>' % self.j

__str__ = __repr__
#@+node:ekr.20050910195107: *7* finishCreate
def finishCreate(self):

    j = self.j ; main = self.main

    self.col = self.main.cols[j]

    self.cells = [cell for cell in main.cells if cell.j == j]

    for cell in self.cells:
        cell.colGroup = self
#@+node:ekr.20050910194752.2: *6* class rowGroupClass
class rowGroupClass:

    @others
#@+node:ekr.20050910194752.3: *7* ctor
def __init__ (self,sudoku,i):

    # g.trace('row',i)
    self.i = i
    self.main = sudoku

    # Set later...
    self.cells = []
    self.row = None
#@+node:ekr.20050911102800.1: *7* __repr__ & __str__
def __repr__ (self):

    return '<row group %d>' % self.i

__str__ = __repr__
#@+node:ekr.20050910195107.1: *7* finishCreate
def finishCreate(self):

    i = self.i ; main = self.main

    self.row = self.main.rows[i]

    self.cells = [cell for cell in main.cells if cell.i == i]

    for cell in self.cells:
        cell.rowGroup = self
#@+node:ekr.20050803075926.7: *5* class cellClass
class cellClass:

    '''A class representing what is known about a particular cell.'''

    @others
#@+node:ekr.20050911144450: *6*  birth
#@+node:ekr.20050803081438: *7*  ctor (cell)
def __init__ (self,sudoku,val,i,j):

    # g.trace('cell',i,j,val)
    self.i = i
    self.j = j
    self.groupNumber = sudoku.groupNumber(i,j)
    self.main = sudoku
    if val is None:     self.values = [digit for digit in digits]
    else:               self.values = [str(val)]
    self.verbose = self.main.verbose

    # Set by group ctors...
    self.colGroup = None
    self.rowGroup = None
    self.squareGroup = None
#@+node:ekr.20050803200724: *7* __repr__ & __str__
def __repr__ (self):

    return 'cell[%d,%d]' % (self.i, self.j)

__str__ = __repr__
#@+node:ekr.20050911113403: *7* finishCreate
def finishCreate(self):

    # g.trace(self)
    assert(self.colGroup)
    assert(self.rowGroup)
    assert(self.squareGroup)

    self.colGroups = [group for group in self.main.squareGroups if self.j in group.colNumbers]
    self.rowGroups = [group for group in self.main.squareGroups if self.i in group.rowNumbers]
#@+node:ekr.20050911112043.2: *6* error & trace
def error (self,s):

    self.main.error(s)

def trace (self,s):

    self.main.trace(s)
#@+node:ekr.20050911092707: *6* reduce
def reduce (self):

    '''Reduce the possible values in self.values: remove an item from
    self.values if any cell in this cell's groups contains only that value.

    Increments self.main.progress or set self.main.valid = False to indicate status.
    '''

    if not self.main.valid: return
    n = len(self.values)
    if n == 0: self.main.valid = False
    if n < 2:  return

    for group in (
        self.colGroup,
        self.rowGroup,
        self.squareGroup,
    ):
        for cell in group.cells:
            if (
                cell is not self and
                len(cell.values) == 1 and 
                cell.values[0] in self.values
            ):
                self.values.remove(cell.values[0])
                n -= 1 
                if n == 0:
                    self.main.valid = False
                    return
                if n == 1:
                    self.setValue(self.values[0])
                else:
                    self.main.progress += 1
#@+node:ekr.20050911111404: *6* removeConflicts
# This is about the most sophisticated deduction that a human could make.

def removeConflicts (self):

    '''Remove a possible value if assigning to this value would make it
    impossible to satisfy a related group.

    Increments self.main.progress or set self.main.valid = False to indicate status. '''

    if not self.main.valid: return
    n = len(self.values)
    if n == 0: self.main.valid = False
    if n < 2:  return

    i,j = self.i,self.j

    colGroups = [group for group in self.colGroups if group != self.squareGroup]
    rowGroups = [group for group in self.rowGroups if group != self.squareGroup]

    # Check for row conflicts.
    for val in self.values:
        for group in rowGroups:
            spots = 0
            for cell in group.cells:
                if i != cell.i and val in cell.values:
                    spots += 1
            if spots == 0:
                self.trace('row conflict: cell: %s, val: %s, group %s' % (self,val,group))
                self.values.remove(val)
                n -= 1 ; self.main.progress += 1
                if n == 0:
                    self.main.valid = False
                return

    # Check for col conflicts.
    for val in self.values:
        for group in colGroups:
            spots = 0
            for cell in group.cells:
                if j != cell.j and val in cell.values:
                    spots += 1
            if spots == 0:
                self.trace('col conflict: cell: %s, val: %s, group %s' % (self,val,group))
                self.values.remove(val)
                n -= 1
                if n == 0:
                    self.main.valid = False
                if n == 1:
                    self.setValue(self.values[0])
                else:
                    self.main.progress += 1
                return
#@+node:ekr.20050911094544: *6* setValue
def setValue (self,val):

    '''We have discovered the proper value for this cell.
    Set self.values=[val] and remove val from self.values from all *other* cells of this groups.'''

    values = self.values
    if self.main.tracing:
        g.trace(self,val,values)
    assert(val in values)
    values.remove(val)
    self.main.progress += 1
    self.values = [str(val)]

    for group in (
        self.colGroup,
        self.rowGroup,
        self.squareGroup,
    ):
        for cell in group.cells:
            if cell is not self:
                if val in cell.values:
                    cell.values.remove(str(val))
                    self.main.progress += 1
                    if len(cell.values) == 0:
                        self.main.valid = False
#@+node:ekr.20050911092707.1: *6* unique
def unique (self):

    '''Set self.values to [val] if val appears in only one place in any of this cells groups.

    Increments self.main.progress or set self.main.valid = False to indicate status.
    '''

    if not self.main.valid: return
    n = len(self.values)
    if n == 0: self.main.valid = False
    if n < 2:  return

    for group in (
        self.colGroup,
        self.rowGroup,
        self.squareGroup,
    ):
        for val in self.values:
            spots = 0
            for cell in group.cells:
                if val in cell.values:
                    spots += 1
            # val is in self.values, and self is in each of its groups.
            assert(spots>0)
            if spots == 1:
                # We have found the only possible place for this value.
                self.setValue(val) # Increments self.main.progress.
                return
#@+node:ekr.20050803134436.1: *6* valIsPossible
def valIsPossible (self,val):

    # g.trace(self,val)
    assert(val is not None)

    for cell in self.rowGroup.cells:
        if cell is not self:
            if len(cell.values) == 1 and cell.values[0] == val:
                if self.main.tracing:
                    g.trace('invalid guess: %s in row %d: %s' % (val,self.i,self.rowGroup.cells))
                return False

    for cell in self.colGroup.cells:
        if cell is not self:
            if len(cell.values) == 1 and cell.values[0] == val:
                if self.main.tracing:
                    g.trace('invalid guess: %s in col %d: %s' % (val,self.j,self.colGroup.cells))
                return False

    return True
#@+node:ekr.20080124063225: *3* sudoku changes
# There are bugs in the guessing logic.
# The following does not fix all of them.

In findGuesses:

    guesses = self.findAllGuesses(vals,cells)
    g.trace('%d raw guess' % len(guesses))
    guesses = [z for z in guesses if self.isValidGuess(z)]

findAllGuesses:def findAllGuesses(self,vals,cells):

    if not vals or not cells:
        return []

    guesses = []
    cell = cells[0]
    for val in vals:
        guess = [g.bunch(cell=cell,val=val)]
        vals2 = [z for z in vals if z != val]
        self.completeGuess(guess,vals2,cells[1:])
        guesses.append(guess)
    return guesses

def completeGuess(self,guess,vals,cells):

    if not vals or not cells:
        return []

    cell = cells[0]
    for val in vals:
        guess.append(g.bunch(cell=cell,val=val))
        vals2 = [z for z in vals if z != val]
        self.completeGuess(guess,vals2,cells[1:])


This works for the following 5* problem:

data = (     # 5 stars
    '1xx2x9xx3',
    'xx6xx3xx7',
    'x3xxxx8xx',
    'xxxx9xxx5',
    '78xxxxx94',
    '9xxx2xxxx',
    'xx1xxxx3x',
    '2xx8xx4xx',
    '8xx5x6xx2',
)
#@+node:ekr.20080222103719: *3* square problem
limit = 1000*1000

def computeSquares(limit):
    squares = {}
    i = 2
    while i < limit:
        n = i*i
        squares[n] = i # big keys
        i += 1
    return squares

def inc(n):
    if n <= 0: return None
    n1 = n ; digits = []
    while n > 0:
        digit = n%10
        if digit == 9: return None
        digits.append(digit)
        n = n//10
    digits.reverse()
    n = len(digits) ; result = 0 ; i = 0
    while i < n:
        digit = digits[i]
        if result > 0 or digit > 0:
            base = (n-i-1)
            result += (digit+1)*(10**base)
        i += 1
    return result

squares = computeSquares(limit)

i = 5
while i < 1000:
    j = i * i
    k = inc(j)
    k2 = squares.get(k)
    if k2:
        assert(k2*k2==k)
        print '**found**',i,'*',i,'=',j,k,'= %d*%d' % (k2,k2)
    # else: print i,j,k
    i += 1
print 'tested all numbers <',i
#@+node:ekr.20150416062327.1: ** Gui
#@+node:ekr.20130816100419.23046: *3* * Full tree preview
@language python

<< docstring >>

from PyQt4 import QtGui, QtCore
from xml.sax.saxutils import escape

def add_html(html, nd):
    """recursively add to an html list with links to nodes"""
    
    unl = nd.get_UNL()
    html.append("<div class='level'>"
        "<div><a href='%s' title='%s'>%s</a></div>" %
        (unl, unl, escape(nd.h)))
    html.append("<pre>%s</pre>"%escape(nd.b))
    for child in nd.children():
        add_html(html, child)
    html.append("</div>")

def make_overview(c):
    """build the overview widget"""

    te = QtGui.QTextBrowser()
    te.setReadOnly(True)
    te.setOpenLinks(False)
    
    def anchorClicked(url, c=c, te=te):
        
        url = str(url.toString())
        g.handleUrl(url,c=c,p=c.p)
        
        if te.ctrl_click:
            te.deleteLater()
        
    te.anchorClicked.connect(anchorClicked)
    
    def mousePressEvent(event, te=te, original=te.mousePressEvent):
        te.ctrl_click = bool(event.modifiers() & QtCore.Qt.ControlModifier)
        original(event)
    
    te.mousePressEvent = mousePressEvent
    
    html = ["""<html><head><style>
    .level .level {margin-left: 1.5em}
    a {text-decoration: none; font-size: 120%}
    </style></head><body>"""]
    
    for nd in c.getSelectedPositions():
        add_html(html, nd)

    html.append("</body></html>") 
    
    html = '\\n'.join(html)
    
    te.setHtml(html)
    
    return te

class OverviewPaneProvider:
    def __init__(self, c):
        self.c = c
        # Careful: we may be unit testing.
        if hasattr(c, 'free_layout'):
            splitter = c.free_layout.get_top_splitter()
            if splitter:
                splitter.register_provider(self)
    def ns_provides(self):
        return[('Overview', '_add_overview_pane')]
    def ns_provide(self, id_):
        if id_ == '_add_overview_pane':
            w = make_overview(c)
            return w
    def ns_title(self, id_):
        if id_ == '_add_overview_pane':
            return "Leo Outline Overview"
    def ns_provider_id(self):
        # used by register_provider() to unregister previously registered
        # providers of the same service
        return "outline overview window"

OverviewPaneProvider(c)
#@+node:ekr.20130816100419.23047: *4* << docstring >>
''' The script sets up Leo to display all the parts of the tree (all bodies
and subheadings) as continuous text, much like a word processor outline.

By Terry Brown

1) Paste the code below into a node, then hit the "run-script" button.

2) Then select a node with some hierarchy, not too much.

3) Then right click on the panel dividers between the tree / body / log
   panes, you should see a context menu with an "Open Window" sub-menu,
   which should contain an "Overview" item.

You should get a continuous view of the hierarchy with clickable
headlines which take you to the node.

You can select multiple nodes in step 2 above, with normal list Ctrl-
or Shift- click operations.  Nodes are shown in the overview in the
order selected.  This is how you'd generate the overview for a whole
outline - i.e. contract the whole outline, click the first top level
node, shift click the last top level node, and then step 3.

You can also embed the overview in a pane in the Leo window by select
"Insert" rather than "Open window" in step 3, click the action button
and select Overview.

This was the low hanging fruit, based on code used in bookmarks.py.  A
refresh button for the outline wouldn't be too hard, but right now
you need to close the window / pane and open it again to refresh.
'''
#@+node:ekr.20140312052111.19343: *3* * Set line height (Qt 4.8 and above)
# As of Qt 4.8, the following script can be executed to set line spacing
# to 190% for the given outline for the current session.

from PyQt4 import QtGui,QtCore,QtGlobal

def hook(tag, kwds, c=c):
  if kwds['c'] != c:
      return
  bodyWidget = c.frame.body.bodyCtrl.widget
  doc = bodyWidget.document()
  for i in range(doc.blockCount()):
      block = doc.findBlockByNumber(i)
      curs = QtGui.QTextCursor(block)
      fmt = block.blockFormat()
      fmt.setLineHeight(190, QtGui.QTextBlockFormat.ProportionalHeight)
      curs.setBlockFormat(fmt)

if True: # Doesn't work: QtCore.QT_VERSION >= QtGlobal.QT_VERSION_CHECK(4,8,0):
    g.registerHandler("select3", hook)
else:
    g.es('this script requires Qt 4.8 or above')
#@+node:ekr.20120328102352.6948: *3* @@button set-style
@language rest

@
http://groups.google.com/group/leo-editor/browse_thread/thread/ba9eb63337467d42/a3f3750d0ce6e847
Here's a one line @button node you can add to myLeoSettings.leo

Important:  setStyleSheet *replaces* the previous stylesheet with the
new stylesheet, so you had best set all the attributes of Leo's
default stylesheet.

As an alternative, if w is any Qt widget, w.setStyleSheet(p.b) will
set the stylesheet for that widget only: the top-level stylesheet (the
stylesheet for c.frame.top.leo_ui) remains unchanged.
@c

@language python

c.frame.top.leo_ui.setStyleSheet(p.b)
# c.frame.top.setStyleSheet(p.b)
#@+node:ekr.20040330095252: *3* Clear log frame
t = c.frame.log.logCtrl
t.delete("1.0","end")
#@+node:ekr.20051110105027.106: *3* Create diagrams using Graphviz and pydot
#@+node:ekr.20051110105027.107: *4* pydot notes by EKR
@nocolor

- I have found it easiest to create pydot objects rather than creating Graphviz strings.  It's the natural way, IMO.

- The pydot documentation is poor.  When you cut through the blah-blah-blah all that is really going on is that you use ctors to create pydot objects.  Typically you specify attributes in the ctors, but there are also getters and setters (various silly redundant flavors) to do this.

- It took me awhile to get the difference between names and labels.  Names are essentially object identifiers, and they are restricted to what are basically C identifiers.  Labels are what are shown in nodes.  The default label is the node's name.   It's a bit strange to use strings instead of Python object references, but it's no big deal.

- The documentation for Graphviz is weak.  Very few examples.  It took me a long time to realize that by default Graphviz lays out nodes and edges independently of the order in which they were created.  The ordering="out" argument to the Dot ctor overrides some parts of the layout algorithm so that nodes are laid out in roughly the definition order.  If you want to place nodes yourself, you can specify their exact position.  This would be feasible to do in a script and I haven't done that yet.

In short, Graphviz and pydot are very impressive tools.  The documentation could be improved, but once one gets the hang of things it is fairly easy to get real work done.
#@+node:ekr.20051110105027.108: *4* pydot docs
@nocolor
#@+node:ekr.20051110105027.109: *5* General note about attributes
The original documentation repeats endlessly the same info about attributes.

Attributes can be set in several ways:

set("attributeName")

set_[attribute name], i.e. set_color, set_fontname

object.attributeName = val

Similarly, you can get attribute values with corresponding getters.
#@+node:ekr.20051110105027.110: *5* Cluster(Graph)
class Cluster(Graph) 

Methods:

__init__(self, graph_name='subG', suppress_disconnected=False, **attrs)

graph_name:
    the cluster's name (the string 'cluster' will be always prepended)

suppress_disconnected:
    False: remove from the cluster any disconnected nodes.


Attributes:

attributes = ['pencolor', 'bgcolor', 'labeljust', 'labelloc', 'URL', 'fontcolor', 'fontsize', 'label', 'fontname', 'lp', 'style', 'target', 'color', 'peripheries', 'fillcolor']
#@+node:ekr.20051110105027.111: *5* Common
class Common 
    Common information to several classes.

Should not be directly used, several classes are derived from this one.

char_range(self, a, b)
Generate a list containing a range of characters.

is_ID(self, s)
Checks whether a string is an dot language ID.

Data:

chars_ID = None
parent_graph = None 



#@+node:ekr.20051110105027.112: *5* Dot(Graph)
class Dot(Graph) 
    A container for handling a dot language file.

This class implements methods to write and process a dot language file.


Methods defined here:

__init__(self, **args)


Attributes:

formats = ['ps', 'ps2', 'hpgl', 'pcl', 'mif', 'pic', 'gd', 'gd2', 'gif', 'jpg', 'jpeg', 'png', 'wbmp', 'ismap', 'imap', 'cmap', 'vrml', 'vtx', 'mp', 'fig', ...]
progs = None
#@+node:ekr.20051110105027.113: *6* create and create_xxx
create(self, prog='dot', format='ps')
Creates and returns a Postscript representation of the graph.

create will write the graph to a temporary dot file and process
it with the program given by 'prog' (which defaults to 'twopi'),
reading the Postscript output and returning it as a string is the
operation is successful.
On failure None is returned.

There's also the preferred possibility of using:

        create_'format'(prog='program')

which are automatically defined for all the supported formats.

[create_ps(), create_gif(), create_dia(), ...]
#@+node:ekr.20051110105027.114: *6* write and write_xxx
write(self, path, prog='dot', format='raw')
Writes a graph to a file.

Given a filename 'path' it will open/create and truncate
such file and write on it a representation of the graph
defined by the dot object and in the format specified by
'format'.

The format 'raw' is used to dump the string representation
of the Dot object, without further processing.

The output can be processed by any of graphviz tools, defined
in 'prog', which defaults to 'dot'

Returns True or False according to the success of the write operation.

There's also the preferred possibility of using:

        write_'format'(path, prog='program')

which are automatically defined for all the supported formats.
[write_ps(), write_gif(), write_dia(), ...]
#@+node:ekr.20051110105027.115: *5* Edge
class Edge(__builtin__.object, Common) 
    A graph edge.

This class represents a graph's edge with all its attributes.

edge(src, dst, attribute=value, ...)

src: source node's name
dst: destination node's name


--------------------------------------------------------------------------------
Methods defined here:

__eq__(self, edge)
Compare two edges.

If the parent graph is directed, arcs linking
node A to B are considered equal and A->B != B->A

If the parent graph is undirected, any edge
connecting two nodes is equal to any other
edge connecting the same nodes, A->B == B->A

__init__(self, src, dst, **attrs)

get_destination(self)
Get the edge's destination node name.

get_source(self)
Get the edges source node name.

parse_node_ref(self, node_str)

set(self, name, value)
Set an attribute value by name.

Given an attribute 'name' it will set its value to 'value'.
There's always the possibility of using the methods:
        set_'name'(value)
which are defined for all the existing attributes.
to_string(self)
Returns a string representation of the edge in dot language.

--------------------------------------------------------------------------------
Data and other attributes defined here:

__dict__ = <dictproxy object>
dictionary for instance variables (if defined)
__weakref__ = <attribute '__weakref__' of 'Edge' objects>
list of weak references to the object (if defined)
attributes = ['style', 'target', 'pos', 'layer', 'tooltip', 'color', 'showboxes', 'URL', 'fontcolor', 'fontsize', 'label', 'fontname', 'comment', 'lp', 'arrowhead', 'arrowsize', 'arrowtail', 'constraint', 'decorate', 'dir', ...]
#@+node:ekr.20051110105027.116: *5* Error
class Error(exceptions.Exception) 
    General error handling class.

Methods defined here:

__init__(self, value)
__str__(self)
#@+node:ekr.20051110105027.117: *5* Graph(Common)
class Graph(__builtin__.object, Common) 
    Class representing a graph in Graphviz's dot language.

This class implements the methods to work on a representation
of a graph in Graphviz's dot language.


Data and other attributes:

__dict__ = <dictproxy object>
dictionary for instance variables (if defined)

__weakref__ = <attribute '__weakref__' of 'Graph' objects>
list of weak references to the object (if defined)

attributes = ['Damping', 'bb', 'center', 'clusterrank', 'compound', 'concentrate', 'defaultdist', 'dim', 'fontpath', 'epsilon', 'layers', 'layersep', 'margin', 'maxiter', 'mclimit', 'mindist', 'pack', 'packmode', 'model', 'page', ...]
#@+node:ekr.20051110105027.118: *6* Graph.__init__
__init__(self, graph_name='G', type='digraph', strict=False, suppress_disconnected=False, simplify=False, **attrs)


graph_name: the graph's name

type: 'graph' or 'digraph'

suppress_disconnected:
    defaults to False, which will remove from the graph any disconnected nodes.

simplify:
    if True it will avoid displaying equal edges, i.e. only one edge between two nodes. removing the duplicated ones.

All the attributes defined in the Graphviz dot language should be supported.

Attributes can be set through the dynamically generated methods:

set_[attribute name], i.e. set_size, set_fontname

or using the instance's attributes:

 Graph.[attribute name], i.e. graph_instance.label, graph_instance.fontname
#@+node:ekr.20051110105027.119: *6* add_edge
add_edge(self, graph_edge)

Adds an edge object to the graph.
#@+node:ekr.20051110105027.120: *6* add_node
add_node(self, graph_node)

Adds a node object to the graph.
#@+node:ekr.20051110105027.121: *6* add_subgraph
add_subgraph(self, sgraph)

Adds an edge object to the graph.
#@+node:ekr.20051110105027.122: *6* getters...
get(self, name)
Get an attribute value by name.

get_'name'() is defined for all attributes.

get_edge(self, src, dst)
Retrieved an edge from the graph.
Returns a list, a single Edge, or None

get_edge_list(self)
Returns the list of Edge instances composing the graph.

get_name(self)
Get the graph's name.

get_node(self, name)
Given a node's name the corresponding Node instance will be returned.
Returns a list, a single Node or None.

get_node_list(self)
Returns the list of Node instances composing the graph.

get_simplify(self)
Get whether to simplify or not.

get_strict(self, val)
Get graph's 'strict' mode (True, False).
This option is only valid for top level graphs.

get_subgraph(self, name)
Given a subgraph's name the corresponding Subgraph instance will be returned.
Returns a list of Subgraphs, a single Subgraph or None.

get_subgraph_list(self)
Returns the list of Subgraph instances in the graph.

get_suppress_disconnected(val)
Get if suppress disconnected is set.

get_type(self)
Get the graph's type, 'graph' or 'digraph'.
#@+node:ekr.20051110105027.123: *6* setters...
set(self, name, value)
Set an attribute value by name.

set_'name'(value) are defined for all the existing attributes.

set_graph_parent(self, parent)
Sets a graph and its elements to point the the parent.
Any subgraph added to a parent graph receives a reference to the parent to access some common data.

set_name(self, graph_name)
Set the graph's name.

set_simplify(self, simplify)
Set whether to simplify or not.
 If True it will avoid displaying equal edges.

set_strict(self, val)
Set graph to 'strict' mode.
This option is only valid for top level graphs.

set_suppress_disconnected(val)
Suppress disconnected nodes in the output graph.

set_type(self, graph_type)
Set the graph's type, 'graph' or 'digraph'.

#@+node:ekr.20051110105027.124: *6* toString
to_string(self, indent='')
Returns a string representation of the graph in dot language.
#@+node:ekr.20051110105027.125: *5* Node(Common)
class Node(__builtin__.object, Common) 
    A graph node.

This class represents a graph's node with all its attributes.


Data and attributes:

__dict__ = <dictproxy object>
dictionary for instance variables (if defined)

__weakref__ = <attribute '__weakref__' of 'Node' objects>
list of weak references to the object (if defined)

attributes = ['showboxes', 'URL', 'fontcolor', 'fontsize', 'label', 'fontname', 'comment', 'root', 'toplabel', 'vertices', 'width', 'z', 'bottomlabel', 'distortion', 'fixedsize', 'group', 'height', 'orientation', 'pin', 'rects', ...]
#@+node:ekr.20051110105027.126: *6* Node.__init__
node(name, attribute=value, ...)

name: node's name

All the attributes defined in the Graphviz dot language should be supported.

__init__(self, name, **attrs)
#@+node:ekr.20051110105027.127: *6* get_name
get_name(self)
Get the node's name.
#@+node:ekr.20051110105027.128: *6* set, set_x and set_name
set(self, name, value)
Set an attribute value by name.

Given an attribute 'name' it will set its value to 'value'.

set_'name'(value) is defined for all the existing attributes.

set_name(self, node_name)
Set the node's name.
#@+node:ekr.20051110105027.129: *6* toString
to_string(self)
Returns a string representation of the node in dot language.
#@+node:ekr.20051110105027.130: *5* Subgraph(Graph)
class Subgraph(Graph) 

Methods:

__init__(self, graph_name='subG', suppress_disconnected=False, **attrs)

graph_name:
    the subgraph's name

suppress_disconnected:
    False: removes from the subgraph any disconnected nodes.

Attributes:

attributes = ['Damping', 'bb', 'center', 'clusterrank', 'compound', 'concentrate', 'defaultdist', 'dim', 'fontpath', 'epsilon', 'layers', 'layersep', 'margin', 'maxiter', 'mclimit', 'mindist', 'pack', 'packmode', 'model', 'page', ...]
#@+node:ekr.20051110105027.131: *5* Functions
#@+node:ekr.20051110105027.132: *6* find_graphviz
find_graphviz()

Locate Graphviz's executables in the system.

Attempts  to locate  graphviz's  executables in a Unix system.
It will look for 'dot', 'twopi' and 'neato' in all the directories
specified in the PATH environment variable.
It will return a dictionary containing the program names as keys
and their paths as values.
#@+node:ekr.20051110105027.133: *6* graph_from_adjacency_matrix
graph_from_adjacency_matrix(matrix, node_prefix='', directed=False)

Creates a basic graph out of an adjacency matrix.

The matrix has to be a list of rows of values
representing an adjacency matrix.
The values can be anything: bool, int, float, as long
as they can evaluate to True or False.
#@+node:ekr.20051110105027.134: *6* graph_from_edges
graph_from_edges(edge_list, node_prefix='', directed=False)

Creates a basic graph out of an edge list.

The edge list has to be a list of tuples representing the nodes connected by the edge.

The values can be anything: bool, int, float, str.

If the graph is undirected by default, it is only
calculated from one of the symmetric halves of the matrix.
#@+node:ekr.20051110105027.135: *6* graph_from_incidence_matrix
graph_from_incidence_matrix(matrix, node_prefix='', directed=False)

Creates a basic graph out of an incidence matrix.

The matrix has to be a list of rows of values
representing an incidence matrix.
The values can be anything: bool, int, float, as long
as they can evaluate to True or False.
#@+node:ekr.20051110105027.136: *4* @url http://www.research.att.com/sw/tools/graphviz/refs.html
#@+node:ekr.20051110105027.137: *4* @url http://dkbza.org/pydot/pydot.html
#@+node:ekr.20051110105027.138: *4* Write an outline using Graphviz
import string

try:
    import pydot
except:
    s = "pydot must be installed"
    print s ; es(s,color="red")
    pydot = None

<< code >>

if pydot:
    graph = pydot.Dot(simplify=True,ordering="out")
    root = g.findNodeInTree(p,"Root")
    addLeoNodesToGraph(root,graph,top=True)
    graph.write_jpeg(r'c:\prog\test\pydotOut.jpg',prog='dot')
#@+node:ekr.20051110105027.139: *5* << code >>
@others
#@+node:ekr.20051110105027.140: *6* addLeoNodesToGraph
def addLeoNodesToGraph(p,graph,top=False):

    # Create p's vnode.
    thisNode = pydot.Node(name=vnodeRepr(p.v),label=vnodeLabel(p.v))
    graph.add_node(thisNode)

    if p.hasChildren():
        child = p.firstChild()
        childNode = addLeoNodesToGraph(child,graph)
        graph.add_node(childNode)
        edge2 = pydot.Edge(tnodeRepr(p.v),vnodeRepr(child.v))
        graph.add_edge(edge2)

        while child.hasNext():
            next = child.next()
            edge = pydot.Edge(vnodeRepr(child.v),vnodeRepr(next.v),dir="both")
            nextNode = addLeoNodesToGraph(next,graph)
            graph.add_node(nextNode)
            graph.add_edge(edge)
            child = next

    tnode = pydot.Node(name=tnodeRepr(p.v),shape="box",label=tnodeLabel(p.v))
    edge1 = pydot.Edge(vnodeRepr(p.v),tnodeRepr(p.v),arrowhead="none")
    graph.add_edge(edge1)
    graph.add_node(tnode)

    if 0: # Confusing.
        if not top and p.v._parent:
            edge = pydot.Edge(vnodeRepr(p.v),vnodeRepr(p.v._parent),
                style="dotted",arrowhead="onormal")
            graph.add_edge(edge)

    if 0: # Marginally useful.
        for v in p.v.vnodeList:
            edge = pydot.Edge(tnodeRepr(p.v),vnodeRepr(v),
                style="dotted",arrowhead="onormal")
            graph.add_edge(edge)

    return thisNode
#@+node:ekr.20051110105027.141: *6* tnode/vnodeLabel
def tnodeLabel(t):

    return "t %d [%d]" % (id(t),len(t.vnodeList))

def vnodeLabel(v):

    return "v %d %s" % (id(v),v.h)
#@+node:ekr.20051110105027.142: *6* tnode/vnodeRepr
def dotId(s):

    """Convert s to a C id"""

    s2 = [ch for ch in s if ch in (string.letters + string.digits + '_')]
    return string.join(s2,'')

def tnodeRepr(t):

    return "t_%d" % id(t)

def vnodeRepr(v):

    return "v_%d_%s" % (id(v),dotId(v.h))
#@+node:ekr.20051110105027.143: *5* Root
#@+node:ekr.20051110105027.144: *6* clone
#@+node:ekr.20051110105027.145: *7* Child1
#@+node:ekr.20051110105027.146: *8* GrandChild
#@+node:ekr.20051110105027.147: *7* Child2
#@+node:ekr.20080105135417: *3* Delete All Icons
# Same as delete-all-icons command (now removed)
# A script seems safer because it can not be executed by mistake.

for p in c.all_positions():
    if hasattr(p.v,"unknownAttributes"):
        a = p.v.unknownAttributes
        iconsList = a.get("icons")
        if iconsList:
            a["icons"] = []
            a["lineYOffset"] = 0
            p.setDirty()
            c.setChanged()
c.redraw()
#@+node:ekr.20071116114235: *3* Print all Tango icons in wiki-markup format
# Prints all icons in the Icons/Tango folder in wiki-markup format.
# This allows the icons to be inserted into Leo's body pane,
# provided that the color_markup and add_directives plugins are enabled.

import glob

folders = (
    'actions','animations','apps','categories','devices',
    'emblems','emotes','mimetypes','places','status',)

for z in folders:
    theDir = g.os_path_join(g.app.loadDir,'..','Icons','Tango','16x16',z)
    print ; print z
    aList = glob.glob(g.os_path_normpath(g.os_path_join(theDir,'*.*')))
    aList.sort()
    aList = ['{picture file=%s} %s' % (z,g.shortFileName(z)) for z in aList]
    aList = [str(z) for z in aList]
    s = g.listToString(aList).replace("'",'').replace('[','').replace(']','')
    print s
#@+node:ekr.20050707183613: *3* Prototype: Ipython Shell
<< imports >>

@others

if 1:
    << use IPShellEmbed >>
else:
    shell = LeoShell('shell')
    # g.redirectStdout()
    g.es_print('-'*40)
    body = p.b
    for line in g.splitLines(body):
        g.es(str(shell.prefilter(line,None)))
    #g.restoreStdout()
#@+node:ekr.20050708110336: *4* << imports >>
import IPython
import IPython.genutils
from IPython.Struct import Struct

import __builtin__
import __main__
import os
import re
import sys

# we need the directory where IPython itself is installed
IPython_dir = os.path.dirname(IPython.__file__)
#@+node:ekr.20050708091220.78: *4* << use IPShellEmbed >>
shell = IPython.Shell.IPShellEmbed (
    argv=[],
    banner='Welcome to IPython in Leo',
    exit_msg='Bye',
    rc_override={
        'confirm_exit':0,
        #'readline':0, # Crashes the interactive interp.
    },
)
shell()
#@+node:ekr.20050708095104: *4* class dummyCache
class dummyCache:

    @others
#@+node:ekr.20050708142137: *5* ctor
def __init__ (self,user_ns):

    self.last_prompt = None
    self.prompt_count = 0
    self.user_ns = user_ns

    if 1:
        self.prompt1 = dummyPrompt('name=prompt1')
        self.prompt2 = dummyPrompt('name=prompt2')
        self.prompt_out = dummyPrompt('name=prompt_out')
    else:
        input_sep='\n'
        self.ps1_str = '>>> '   ### self._set_prompt_str(ps1,'In [\\#]: ','>>> ')
        self.ps2_str = '... '   ### self._set_prompt_str(ps2,'   .\\D.: ','... ')
        self.ps_out_str = ','   ### self._set_prompt_str(ps_out,'Out[\\#]: ','')
        pad_left=True

        self.prompt1 = IPython.Prompts.Prompt1(self,
            sep=input_sep,prompt=self.ps1_str,pad_left=pad_left)
        self.prompt2 = IPython.Prompts.Prompt2(self,
            prompt=self.ps2_str,pad_left=pad_left)
        self.prompt_out = IPython.Prompts.PromptOut(self,
            sep='',prompt=self.ps_out_str,pad_left=pad_left)

    self.last_prompt = self.prompt1 # Total kludge.
#@+node:ekr.20050708142137.2: *5* All others
def __len__ (self):
    return 0

def insert(self,n,line):
    pass

def pop(self):
    return ''
#@+node:ekr.20050708143008: *4* class dummyPrompt
class dummyPrompt (IPython.Prompts.BasePrompt):

    """Interactive prompt similar to Mathematica's."""

	@others
#@+node:ekr.20050708143008.2: *5* __init__
def __init__(self,cache=None,sep=None,prompt=None,pad_left=False,name='prompt'):

    self.name = name
#@+node:ekr.20050708143008.3: *5* set_p_str
def set_p_str(self):
    """ Set the interpolating prompt strings.

    This must be called every time the color settings change, because the
    prompt_specials global may have changed."""

    return ###

    import os,time  # needed in locals for prompt string handling
    loc = locals()
    self.p_str = ItplNS('%s%s%s' %
                        ('${self.sep}${self.col_p}',
                         multiple_replace(prompt_specials, self.p_template),
                         '${self.col_norm}'),self.cache.user_ns,loc)

    self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
                                                 self.p_template),
                                self.cache.user_ns,loc)
#@+node:ekr.20050708143008.4: *5* write
def write(self,msg):  # dbg

    return '' ###

    sys.stdout.write(msg)
    return ''
#@+node:ekr.20050708143008.5: *5* __str__
def __str__(self):
    """Return a string form of the prompt.

    This for is useful for continuation and output prompts, since it is
    left-padded to match lengths with the primary one (if the
    self.pad_left attribute is set)."""

    return self.name ###

    out_str = str_safe(self.p_str)
    if self.pad_left:
        # We must find the amount of padding required to match lengths,
        # taking the color escapes (which are invisible on-screen) into
        # account.
        esc_pad = len(out_str) - len(str_safe(self.p_str_nocolor))
        format = '%%%ss' % (len(str(self.cache.last_prompt))+esc_pad)
        return format % out_str
    else:
        return out_str
#@+node:ekr.20050708144144.1: *5* Unchanged
if 0:
    @others
#@+node:ekr.20050708143008.6: *6* cwd_filt
# these path filters are put in as methods so that we can control the
# namespace where the prompt strings get evaluated.

def cwd_filt(self,depth):

    """Return the last depth elements of the current working directory.

    $HOME is always replaced with '~'.
    If depth==0, the full path is returned."""

    cwd = os.getcwd().replace(HOME,"~")
    out = os.sep.join(cwd.split(os.sep)[-depth:])
    if out:
        return out
    else:
        return os.sep
#@+node:ekr.20050708143008.7: *6* cwd_filt2
def cwd_filt2(self,depth):
    """Return the last depth elements of the current working directory.

    $HOME is always replaced with '~'.
    If depth==0, the full path is returned."""

    cwd = os.getcwd().replace(HOME,"~").split(os.sep)
    if '~' in cwd and len(cwd) == depth+1:
        depth += 1
    out = os.sep.join(cwd[-depth:])
    if out:
        return out
    else:
        return os.sep
#@+node:ekr.20050708144144.2: *5* auto_rewrite
def auto_rewrite(self,*args,**keys):
    return ''
#@+node:ekr.20050708091220.76: *4* class LeoShell
class LeoShell (IPython.iplib.InteractiveShell):

    @others

    # Set the default prefilter() function (this can be user-overridden)
    prefilter = _prefilter
#@+node:ekr.20050708091220.77: *5* ctor
def __init__ (self,name):

    self.shell = self
    self.name = name

    << directory stuff >>
    << set sensible command line defaults for self.rc >>
    << define regexp's >>
    << define escape stuff >>
    << define namespaces >>
    << create alias table >>
    << define inpsector >>

    self.inputcache = dummyCache(self.user_ns)
    self.outputcache = dummyCache(self.user_ns)
    self.CACHELENGTH = 0
#@+node:ekr.20050708110239: *6* << directory stuff >>
# EKR: take from make_IPython.

# Platform-dependent suffix and directory names
if os.name == 'posix':
    rc_suffix = ''
    ipdir_def = '.ipython'
else:
    rc_suffix = '.ini'
    ipdir_def = '_ipython'

# default directory for configuration
if 1: ### Leo
    ipythondir = g.app.loadDir
else:
    ipythondir = os.path.abspath(
        os.environ.get('IPYTHONDIR',
        os.path.join(IP.home_dir,ipdir_def)))
#@+node:ekr.20050708105742: *6* << set sensible command line defaults for self.rc >>
# EKR: take from make_IPython

# This should have everything from  cmdline_opts and cmdline_only
self.rc = Struct(
    autocall = 1,
    autoindent=0,
    automagic = 1,
    banner = 1,
    cache_size = 1000,
    c = '',
    classic = 0,
    colors = 'NoColor',
    color_info = 0,
    confirm_exit = 1,
    debug = 0,
    deep_reload = 0,
    editor = '0',
    help = 0,
    ignore = 0,
    ipythondir = ipythondir,
    log = 0,
    logfile = '',
    logplay = '',
    multi_line_specials = 1,
    messages = 1,
    nosep = 0,
    pdb = 0,
    pprint = 0,
    profile = '',
    prompt_in1 = 'In [\\#]:',
    prompt_in2 = '   .\\D.:',
    prompt_out = 'Out[\\#]:',
    prompts_pad_left = 1,
    quick = 0,
    readline = 1,
    readline_merge_completions = 1,
    readline_omit__names = 0,
    rcfile = 'ipythonrc' + rc_suffix,
    screen_length = 0,
    separate_in = '\n',
    separate_out = '\n',
    separate_out2 = '',
    system_verbose = 0,
    gthread = 0,
    qthread = 0,
    wthread = 0,
    pylab = 0,
    tk = 0,
    upgrade = 0,
    Version = 0,
    xmode = 'Verbose',
    magic_docstrings = 0,  # undocumented, for doc generation
)
#@+node:ekr.20050708093114: *6* << define regexp's >>
# Don't get carried away with trying to make the autocalling catch too
# much:  it's better to be conservative rather than to trigger hidden
# evals() somewhere and end up causing side effects.

self.line_split = re.compile(
    r'^([\s*,;/])'
    r'([\?\w\.]+\w*\s*)'
    r'(\(?.*$)'
)

# RegExp to identify potential function names
self.re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')

# RegExp to exclude strings with this start from autocalling
self.re_exclude_auto = re.compile('^[!=()<>,\*/\+-]|^is ')

# try to catch also methods for stuff in lists/tuples/dicts: off
# (experimental). For this to work, the line_split regexp would need
# to be modified so it wouldn't break things at '['. That line is
# nasty enough that I shouldn't change it until I can test it _well_.
#self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
#@+node:ekr.20050708093224: *6* << define escape stuff >>
# escapes for automatic behavior on the command line
self.ESC_SHELL = '!'
self.ESC_HELP  = '?'
self.ESC_MAGIC = '%'
self.ESC_QUOTE = ','
self.ESC_QUOTE2 = ';'
self.ESC_PAREN = '/'

# And their associated handlers
self.esc_handlers = {
    self.ESC_PAREN: self.handle_auto,
    self.ESC_QUOTE: self.handle_auto,
    self.ESC_QUOTE2:self.handle_auto,
    self.ESC_MAGIC: self.handle_magic,
    self.ESC_HELP:  self.handle_help,
    self.ESC_SHELL: self.handle_shell_escape,
}
#@+node:ekr.20050708093433: *6* << define namespaces >>
# Set __name__ to __main__ to better match the behavior of the normal interpreter.

self.user_ns = {
    '__name__'     :'__main__',
    '__builtins__' : __builtin__,
}

self.internal_ns = __main__.__dict__.copy()
#@+node:ekr.20050708094606.1: *6* << create alias table >>
# dict of names to be treated as system aliases.  Each entry in the
# alias table must be a 2-tuple of the form (N,name), where N is the
# number of positional arguments of the alias.
self.alias_table = {}
#@+node:ekr.20050708150223: *6* << define inpsector >>
ins_colors = IPython.OInspect.InspectColors
code_colors = IPython.PyColorize.ANSICodeColors

self.inspector = IPython.OInspect.Inspector(ins_colors,code_colors,'NoColor')
#@+node:ekr.20050708105323.2: *5* usage
def usage(self):

    return 'A usage message'
#@+node:ekr.20050708095104.1: *5* log
def log(self,line,continuation=None):

    # Called by the logger (not sure how).
    if 0:
        g.trace(line)
#@+node:ekr.20050708113006.2: *5* system
def system(self,s):

    g.trace(s)
    pass
#@+node:ekr.20050708152111: *5* _prefilter
def _prefilter(self, line, continue_prompt):
    """Calls different preprocessors, depending on the form of line."""

    << about this function >>

    # save the line away in case we crash, so the post-mortem handler can record it
    self._last_input_line = line

    #print '***line: <%s>' % line # dbg
    if not line.strip():
        << handle empty line >>

    # print '***cont',continue_prompt  # dbg
    # special handlers are only allowed for single line statements
    if continue_prompt and not self.rc.multi_line_specials:
        return self.handle_normal(line,continue_prompt)

    # Get the structure of the input
    pre,iFun,theRest = self.split_user_input(line)
    #print 'pre <%s> iFun <%s> rest <%s>' % (pre,iFun,theRest)  # dbg

    << First check for explicit escapes in the last/first character >>
    << Next, check if we can automatically execute this thing >>
    << Let's try to find if the input line is a magic fn >>
    << execute comparisons, assignsments or function calls >>

    # If we get here, we have a normal Python line. Log and return.
    return self.handle_normal(line,continue_prompt)
#@+node:ekr.20050708152111.1: *6* << about this function >>
# All handlers *must* return a value, even if it's blank ('').

# Lines are NOT logged here. Handlers should process the line as
# needed, update the cache AND log it (so that the input cache array
# stays synced).

# This function is _very_ delicate, and since it's also the one which
# determines IPython's response to user input, it must be as efficient
# as possible.  For this reason it has _many_ returns in it, trying
# always to exit as quickly as it can figure out what it needs to do.

# This function is the main responsible for maintaining IPython's
# behavior respectful of Python's semantics.  So be _very_ careful if
# making changes to anything here.
#@+node:ekr.20050708152111.2: *6* << handle empty line >>
# the input history needs to track even empty lines

if not continue_prompt:
    self.outputcache.prompt_count -= 1

return self.handle_normal('',continue_prompt)
#@+node:ekr.20050708152111.3: *6* << First check for explicit escapes in the last/first character >>
handler = None
if line[-1] == self.ESC_HELP:
    handler = self.esc_handlers.get(line[-1])  # the ? can be at the end

if handler is None:
    # look at the first character of iFun, NOT of line, so we skip
    # leading whitespace in multiline input
    handler = self.esc_handlers.get(iFun[0:1])

if handler is not None:
    return handler(line,continue_prompt,pre,iFun,theRest)

# Emacs ipython-mode tags certain input lines
if line.endswith('# PYTHON-MODE'):
    return self.handle_emacs(line,continue_prompt)
#@+node:ekr.20050708152111.4: *6* << Next, check if we can automatically execute this thing >>
# Allow ! in multi-line statements if multi_line_specials is on:
if (
    continue_prompt and self.rc.multi_line_specials and iFun.startswith(self.ESC_SHELL)
):
    return self.handle_shell_escape(line,continue_prompt,
            pre=pre,iFun=iFun,theRest=theRest)
#@+node:ekr.20050708152111.5: *6* << Let's try to find if the input line is a magic fn >>
oinfo = None

if hasattr(self,'magic_'+iFun):
    oinfo = self._ofind(iFun) # FIXME - _ofind is part of Magic
    if oinfo['ismagic']:
        # Be careful not to call magics when a variable assignment is
        # being made (ls='hi', for example)
        if (
            self.rc.automagic and
            (len(theRest)==0 or theRest[0] not in '!=()<>,') and 
            (self.rc.multi_line_specials or not continue_prompt)
        ):
            return self.handle_magic(line,continue_prompt,pre,iFun,theRest)
        else:
            return self.handle_normal(line,continue_prompt)
#@+node:ekr.20050708152111.6: *6* << execute comparisons, assignsments or function calls >>
# If the rest of the line begins with an (in)equality, assginment or
# function call, we should not call _ofind but simply execute it.
# This avoids spurious geattr() accesses on objects upon assignment.
#
# It also allows users to assign to either alias or magic names true
# python variables (the magic/alias systems always take second seat to
# true python code).
if theRest and theRest[0] in '!=()':
    return self.handle_normal(line,continue_prompt)

if oinfo is None:
    oinfo = self._ofind(iFun) # FIXME - _ofind is part of Magic

if not oinfo['found']:
    return self.handle_normal(line,continue_prompt)
else:
    #print 'iFun <%s> rest <%s>' % (iFun,theRest) # dbg
    if oinfo['isalias']:
        return self.handle_alias(line,continue_prompt,
                                     pre,iFun,theRest)

    if self.rc.autocall and \
           not self.re_exclude_auto.match(theRest) and \
           self.re_fun_name.match(iFun) and \
           callable(oinfo['obj']) :
        #print 'going auto'  # dbg
        return self.handle_auto(line,continue_prompt,pre,iFun,theRest)
    else:
        #print 'was callable?', callable(oinfo['obj'])  # dbg
        return self.handle_normal(line,continue_prompt)
#@+node:ekr.20050708152111.7: *5* prefilter & helpers
#@+node:ekr.20050708152111.9: *6* _prefilter
def _prefilter(self, line, continue_prompt):
    """Calls different preprocessors, depending on the form of line."""

    << about this function >>

    #if line.startswith('%crash'): raise RuntimeError,'Crash now!'  # dbg

    # save the line away in case we crash, so the post-mortem handler can record it
    self._last_input_line = line

    if not line.strip():
        return ''

    # special handlers are only allowed for single line statements
    if continue_prompt and not self.rc.multi_line_specials:
        return line

    # Get the structure of the input
    pre,iFun,theRest = self.split_user_input(line)
    #print 'pre <%s> iFun <%s> rest <%s>' % (pre,iFun,theRest)  # dbg

    << First check for explicit escapes in the last/first character >>
    << Next, check if we can automatically execute this thing >>
    << Let's try to find if the input line is a magic fn >>
    << execute comparisons, assignsments or function calls >>

    # A normal Python line.
    return line
#@+node:ekr.20050708152111.10: *7* << about this function >>
# All handlers *must* return a value, even if it's blank ('').

# Lines are NOT logged here. Handlers should process the line as
# needed, update the cache AND log it (so that the input cache array
# stays synced).

# This function is the main responsible for maintaining IPython's
# behavior respectful of Python's semantics.  So be _very_ careful if
# making changes to anything here.
#@+node:ekr.20050708152111.12: *7* << First check for explicit escapes in the last/first character >>
handler = None
if line[-1] == self.ESC_HELP:
    handler = self.esc_handlers.get(line[-1])  # the ? can be at the end

if handler is None:
    # look at the first character of iFun, NOT of line, so we skip
    # leading whitespace in multiline input
    handler = self.esc_handlers.get(iFun[0:1])

if handler is not None:
    return handler(line,continue_prompt,pre,iFun,theRest)

# Emacs ipython-mode tags certain input lines
if line.endswith('# PYTHON-MODE'):
    return self.handle_emacs(line,continue_prompt)
#@+node:ekr.20050708152111.13: *7* << Next, check if we can automatically execute this thing >>
# Allow ! in multi-line statements if multi_line_specials is on:
if (
    continue_prompt and self.rc.multi_line_specials and iFun.startswith(self.ESC_SHELL)
):
    return self.handle_shell_escape(line,continue_prompt,pre=pre,iFun=iFun,theRest=theRest)
#@+node:ekr.20050708152111.14: *7* << Let's try to find if the input line is a magic fn >>
oinfo = None

if hasattr(self,'magic_'+iFun):
    oinfo = self._ofind(iFun) # FIXME - _ofind is part of Magic
    if oinfo['ismagic']:
        # Be careful not to call magics when a variable assignment is
        # being made (ls='hi', for example)
        if (
            self.rc.automagic and
            (len(theRest)==0 or theRest[0] not in '!=()<>,') and 
            (self.rc.multi_line_specials or not continue_prompt)
        ):
            return self.handle_magic(line,continue_prompt,pre,iFun,theRest)
        else:
            return line
#@+node:ekr.20050708152111.15: *7* << execute comparisons, assignsments or function calls >>
# If the rest of the line begins with an (in)equality, assginment or
# function call, we should not call _ofind but simply execute it.
# This avoids spurious geattr() accesses on objects upon assignment.
#
# It also allows users to assign to either alias or magic names true
# python variables (the magic/alias systems always take second seat to
# true python code).
if theRest and theRest[0] in '!=()':
    return line

if oinfo is None:
    oinfo = self._ofind(iFun) # FIXME - _ofind is part of Magic

if not oinfo['found']:
    return line

if oinfo['isalias']:
    return self.handle_alias(line,continue_prompt,pre,iFun,theRest)

if (self.rc.autocall and not self.re_exclude_auto.match(theRest) and 
    self.re_fun_name.match(iFun) and callable(oinfo['obj'])
):
    return self.handle_auto(line,continue_prompt,pre,iFun,theRest)
else:
    return line
#@+node:ekr.20050708165401.1: *6* handle_normal ( no longer used)
def handle_normal(self,line,continue_prompt=None,pre=None,iFun=None,theRest=None):
    """Handle normal input lines. Use as a template for handlers."""

    g.trace(line)

    if 0:
        self.log(line,continue_prompt)
        self.update_cache(line)

    return line
#@+node:ekr.20050708152111.17: *6* handle_alias (done)
def handle_alias(self,line,continue_prompt=None,pre=None,iFun=None,theRest=None):

    """Handle alias input lines. """

    theRest = esc_quotes(theRest)

    line_out = "%s%s.call_alias('%s','%s')" % (pre,self.name,iFun,theRest)

    return line_out
#@+node:ekr.20050708152111.18: *6* handle_shell_escape (needs work)
def handle_shell_escape(self, line, continue_prompt=None,pre=None,iFun=None,theRest=None):

    """Execute the line in a shell, empty return value"""

    # Example of a special handler. Others follow a similar pattern.
    if continue_prompt:  # multi-line statements
        if iFun.startswith('!!'):
            print 'SyntaxError: !! is not allowed in multiline statements'
            return pre
        else:
            cmd = ("%s %s" % (iFun[1:],theRest)).replace('"','\\"')
            line_out = '%s%s.system("%s")' % (pre,self.name,cmd)
    else: # single-line input
        if line.startswith('!!'):
            # rewrite iFun/theRest to properly hold the call to %sx and
            # the actual command to be executed, so handle_magic can work
            # correctly
            theRest = '%s %s' % (iFun[2:],theRest)
            iFun = 'sx'
            return self.handle_magic('%ssx %s' % (
                self.ESC_MAGIC,line[2:]),continue_prompt,pre,iFun,theRest)
        else:
            cmd = esc_quotes(line[1:])
            line_out = '%s.system("%s")' % (self.name,cmd)

    return line_out
#@+node:ekr.20050708152111.19: *6* handle_magic (done)
def handle_magic(self, line, continue_prompt=None,pre=None,iFun=None,theRest=None):

    """Execute magic functions.

    Also log them with a prepended # so the log is clean Python."""

    return '%sipmagic("%s")' % (pre,esc_quotes('%s %s' % (iFun,theRest)))
#@+node:ekr.20050708152111.20: *6* handle_auto (may need work)
def handle_auto(self, line, continue_prompt=None,pre=None,iFun=None,theRest=None):

    """Hande lines which can be auto-executed, quoting if requested."""

    # This should only be active for single-line input!
    if continue_prompt:
        return line
    elif pre == self.ESC_QUOTE: # Auto-quote splitting on whitespace
        return '%s("%s")\n' % (iFun,'", "'.join(theRest.split()) )
    elif pre == self.ESC_QUOTE2: # Auto-quote whole string
        return '%s("%s")\n' % (iFun,theRest)
    else: # Auto-paren
        if theRest[0:1] in ('=','['):
            # Don't autocall in these cases.  They can be rebindings of an existing callable's name,
            # or item access for an object which is BOTH callable and implements __getitem__.
            return '%s %s\n' % (iFun,theRest)
        if theRest.endswith(';'):
            return '%s(%s);\n' % (iFun.rstrip(),theRest[:-1])
        else:
            return '%s(%s)\n' % (iFun.rstrip(),theRest)
#@+node:ekr.20050708152111.21: *6* handle_help (may need work) (and it's stupid anyway)
def handle_help(self, line, continue_prompt=None,pre=None,iFun=None,theRest=None):
    """Try to get some help for the object.

    obj? or ?obj   -> basic information.
    obj?? or ??obj -> more details.
    """

    # Don't process lines which would be otherwise valid python, such as "x=1 # what?"
    try:
        code.compile_command(line)
    except SyntaxError: # Only handle stuff which is NOT valid syntax
        if line[0]==self.ESC_HELP:
            line = line[1:]
        elif line[-1]==self.ESC_HELP:
            line = line[:-1]
        if line:
            self.magic_pinfo(line)
        else:
            page(self.usage,screen_lines=self.rc.screen_length)
        return '' # Empty string is needed here!
    except: pass

    return line
#@+node:ekr.20050708152111.22: *6* handle_emacs
def handle_emacs(self,line,continue_prompt=None,pre=None,iFun=None,theRest=None):
    """Handle input lines marked by python-mode."""

    # Currently, nothing is done.
    # The input cache shouldn't be updated
    return line
#@+node:ekr.20050708152111.23: *6* safe_execfile (not used)
#@+node:ekr.20050708152111.24: *6* split_user_input
if 0: # ref
    self.line_split = re.compile(
        r'^([\s*,;/])'          # Groups[0]: s, followed by special chars: , ; or /
        r'([\?\w\.]+\w*\s*)'    # Groups[1]: one char,
        r'(\(?.*$)' )           # Groups[2]: arg list

def split_user_input(self,line):

    """Split user input into pre-char, function part and rest."""

    lsplit = self.line_split.match(line)

    if lsplit is None:  # no regexp match returns None
        try:
            iFun,theRest = line.split(None,1)
        except ValueError:
            iFun,theRest = line,''
        pre = re.match('^(\s*)(.*)',line).groups()[0]
    else:
        pre,iFun,theRest = lsplit.groups()

    print 'line:<%s>' % line # dbg
    print 'pre <%s> iFun <%s> rest <%s>' % (pre,iFun.strip(),theRest) # dbg

    return pre,iFun.strip(),theRest
#@+node:ekr.20050708152111.25: *6* update_cache
def update_cache(self, line):

    """puts line into cache"""

    pass
#@+node:ekr.20050708105323.3: *4* ipmagic & palias
def ipmagic(s):
    g.trace()
    return IPython.iplib.ipmagic(s)

def ipalias(s):
    g.trace()
    return IPython.iplib.ipalias(s)
#@+node:ekr.20050708165531.1: *4* esc_quotes
def esc_quotes(s):

    return IPython.iplib.esc_quotes(s)
#@+node:ekr.20140704052551.17946: *3* Set icon for .leo files
'''
This leo script _almost_ adds an icon to .leo files in Windows. I say
almost in that on my system it runs without error, the associated registry
key is created and contains the right path, but Windows still doesn't know
what to do with the file.

It's intended to be run after "create-leobat" has been run and the Leo.File
filetype is already present.

Anyone else have some ideas how to improve it?
'''

from _winreg import *
def register_leo_icon():
    '''Tell Windows what icon to use for the  Leo.File filetype (.leo)

    Resources:

http://stackoverflow.com/questions/2331690/how-to-set-a-icon-file-while-creating-file

http://stackoverflow.com/questions/771689/how-can-i-set-an-icon-for-my-own-file-extension
    '''

    icon = "%s\\Icons\\LeoDoc.ico" % g.computeLeoDir()

    g.es("\\nAttempting to register leo icon with .leo files...")

    if g.os_path_exists(icon):
        g.es("Found:", icon)
        myTestKey = OpenKey(HKEY_CLASSES_ROOT, "Leo.File")
        iconKey= CreateKey(myTestKey, "DefaultIcon")
        CloseKey(myTestKey)

        SetValue(iconKey, None, REG_SZ, icon)
        CloseKey(iconKey)
        g.es("Registered!")
    else:
        g.es("LeoDoc.ico not in expected location, can't continue.")
#@+node:ekr.20150416063804.1: *3* Tk scripts
# These are obsolete, unless Tk is run in a separate process.
#@+node:ekr.20041220080654: *4* Prototype: Setting Tk config values safely
import Tkinter as Tk

<< documentation about how to set general options >>
t = Tk.Text()

print '-' * 20

settings = (
    ('height','xyz'),
    ('width',30),
    ('xyzzy',2),
)

widget_keys = t.keys() # List of all valid settings for this widget.
widget_keys.sort()

# Make a list of valid settings, and warn about invalid settings.
valid_settings = []
for key,val in settings:
    if key in widget_keys:
        setting = key,val
        valid_settings.append(setting)
    else:
        s = "'%s' is not a valid Tk option for this widget" % key
        print s ; g.es(s,color='blue')
valid_settings.sort()

print 'before changes...'
for key,val in valid_settings:
    print '%s = %s' % (key,str(t.cget(key)))

for key,val in valid_settings:
    d = {key:val}
    try:
        if 1: # The preferred way, using the 'extended call syntax'.
            # This was introduced in Python 2.0.
            t.configure(**d)
        else: # The Python 1.x way.  Deprecated since Python 2.3.
            apply(t.configure,[],d)
    except Tk.TclError:
        s = "Tk exception setting '%s' to %s" % (key,repr(val))
        print s ; g.es(s,color='blue')

print 'after changes...'
for key,val in valid_settings:
    print '%s = %s' % (key, str(t.cget(key)))

if 0:
    print ; print 'all keys...'
    for key in widget_keys:
        print '%s = %s' % (key, str(t.cget(key)))
#@+node:ekr.20041220091350: *5* << documentation about how to set general options >>
@nocolor
@
The keyword argument syntax is of course much more elegant, and less error prone. However, for compatibility with existing code, Tkinter still supports the older syntax. You shouldn't use this syntax in new programs, even if it might be tempting in some cases. For example, if you create a custom widget which needs to pass configuration options along to its parent class, you may come up with something like:

@color

    def __init__(self, master, **kw):
        Canvas.__init__(self, master, kw) # kw is a dictionary

@nocolor
This works just fine with the current version of Tkinter, but it may not work with future versions. A more general approach is to use the apply function:
@color

    def __init__(self, master, **kw):
        apply(Canvas.__init__, (self, master), kw)

@nocolor
The apply function takes a function (an unbound method, in this case), a tuple with arguments (which must include self since we're calling an unbound method), and optionally, a dictionary which provides the keyword arguments.

--------- Apply is deprecated ---------

apply( function, args[, keywords]) 

The function argument must be a callable object (a user-defined or built-in function or method, or a class object) and the args argument must be a sequence. The function is called with args as the argument list; the number of arguments is the length of the tuple. If the optional keywords argument is present, it must be a dictionary whose keys are strings. It specifies keyword arguments to be added to the end of the argument list.

Calling apply() is different from just calling function(args), since in that case there is always exactly one argument. The use of apply() is equivalent to function(*args, **keywords). Use of apply() is not necessary since the ``extended call syntax,'' as used in the last example, is completely equivalent. 

Deprecated since release 2.3. Use the extended call syntax instead, as described above.
#@+node:ekr.20050726101926: *4* Prototype: TK: keyPressed
def keyPressed( self, event ):

    << create the command >>
    self.kTconsume = self.kRconsume = consume = self.emacs.masterCommand(event,command)
    if consume:
        # Block the event from going elsewhere, like the DocumentModel.
        event.consume()
        return

    kc = event.getKeyChar()
    if self.tab_for_colon and kc == '\n':
        event.consume()
        self.insertPreviousLeadAndNewline()
    if self.completers.has_key(kc):
        << handle auto completion >>
    elif kc == '\t' and self.tab_width == -4:
        << handle auto-tabbing >>
#@+node:ekr.20050726101926.1: *5* << create the command >>
modifiers = event.getModifiers()
mtxt = event.getKeyModifiersText(modifiers)
ktxt = event.getKeyText(event.getKeyCode())

if mtxt == ktxt:
    command = mtxt
else:
    command = '%s %s' % (mtxt,ktxt).strip()
#@+node:ekr.20050726101926.2: *5* << handle auto completion >>
editor = self.emacs.editor
doc = editor.getDocument()
pos = editor.getCaretPosition()
try:
    pc = doc.getText( pos -1, 1 )
    if pc in ( '"', "'" ): return
except: pass

event.consume()
self.kTconsume = True
self.kRconsume = True
ac = self.completers[ kc ]
doc.insertString( pos, '%s%s' %( kc, ac ), None )
editor.setCaretPosition( pos + 1 )
if hasattr(self.emacs.c.frame.body.editor, "autocompleter"):
    self.emacs.c.frame.body.editor.autocompleter.hideAutoBox() 
#@+node:ekr.20050726101926.3: *5* << handle auto-tabbing >>
self.kTconsume = True
self.kRconsume = True
event.consume()
editor = self.emacs.editor
doc = editor.getDocument()
pos = editor.getCaretPosition()
try:
    doc.insertString( pos, " " * 4, None )
except: pass
#@+node:ekr.20050219073752.1: *4* Tk: Add a menu item after Open With
def callback(*args,**keys):
    g.trace('after Open With')

# Get the actual Tkinter menu.
fileMenu = c.frame.menu.getMenu('File')

# Now use raw Tkinter calls to insert the menu.
fileMenu.insert(3,'command',label='Test',command=callback) 
#@+node:ekr.20051011211253: *4* Tk: Debugger canvas
import Tkinter as Tk
import tkFont

images = {}

@others

h = 500 ; w = 900

top = Tk.Toplevel(None) ; top.title("Debugger")

outer = Tk.Frame(top,height=h,width=w)
outer.pack(expand=1,fill='both')

canvas = Tk.Canvas(outer,background='LightSteelBlue1',width=14)
canvas.pack(side='left',fill='y',expand=0)

text = Tk.Text(outer)
text.pack(side='left',fill='both',expand=1,pady=0,padx=0)

line_h = getLineHeight(text)
# print line_h
image = getImage('minusnode.gif',canvas)

y = line_h / 2 - 2
while y < h:
    id = canvas.create_image(4,y,image=image,anchor="nw")
    y += line_h
#@+node:ekr.20051011213138: *5* getImage
def getImage(name,canvas):

    icon = images.get(name)
    if icon: return icon

    try:
        fullname = g.os_path_join(g.app.loadDir,"..","Icons",name)
        fullname = g.os_path_normpath(fullname)
        image = Tk.PhotoImage(master=canvas,file=fullname)
        images [name] = image
        return image
    except:
        g.es("Exception loading: " + fullname)
        g.es_exception()
        return None
#@+node:ekr.20051011215038: *5* getLineHeight
def getLineHeight (text):

    try:
        fontName = text.cget('font')
        font = tkFont.Font(font=fontName)
        metrics = font.metrics()
        return metrics ["linespace"]
    except Exception:
        g.es("exception setting outline line height")
        g.es_exception()
        return 20 # default
#@+node:EKR.20040626212434: *4* TK: Drawing experiments
#@+node:EKR.20040626212434.1: *5* Rectangles & ovals
import Tkinter as Tk
import random as r ; rand = r.randint

top = Tk.Toplevel(None) ; top.title("Drawing")
canvas = Tk.Canvas(top,height="5i",width="9i") # ,background="white")
canvas.pack() ; top.update()

mincolor,maxcolor=125,225

for n in xrange(5000):
    x,y = rand(0,900),rand(0,500)
    w = rand(1,10) ; h = w * r.uniform(0.5,1.5)
    color = "#%02x%02x%02x" % (rand(0,maxcolor/2),rand(mincolor,maxcolor),rand(mincolor,maxcolor))
    kind = rand(1,3)
    sign = rand(-1,1) # rand(0,2)-1
    if kind == 1:
        canvas.create_rectangle(x,y,x+w,y+h,fill=color,width=0)
    elif kind == 2:
        canvas.create_oval(x,y,x+w,y+h,fill=color,width=0)
    else:
        canvas.create_line(x,y,x+sign*5*w,y+5*h,fill=color)
    if 0: # Redrawing slows things down a lot.
        if (n % 1000) == 0: top.update()
#@+node:EKR.20040626212434.2: *5* Lines & arcs
import Tkinter as Tk
import random as r

top = Tk.Toplevel(None) ; top.title("Drawing")
canvas = Tk.Canvas(top, height = "5i", width = "9i")
canvas.pack() ; top.update()
rand = r.randint

x,y = 10,10
mincolor,maxcolor=125,225

for n in xrange(2000):
    x2,y2 = rand(0,900),rand(0,500)
    color = "#%02x%02x%02x" % (rand(mincolor,maxcolor),rand(mincolor,maxcolor),rand(mincolor,maxcolor))
    width = "%fm" % r.uniform(0.1,0.6)
    canvas.create_line(x,y,x2,y2,fill=color,width=width)
    extent = rand(180,270)
    canvas.create_arc(x,y,x2,y2,outline=color,width=width,style="arc",extent=extent)
    x,y = x2,y2
    # if (n % 1000) == 0: top.update()
#@+node:EKR.20040626213007: *5* Paul Klee
import Tkinter as Tk
import random as r ; rand = r.randint

top = Tk.Toplevel(None) ; top.title("Paul Klee")
canvas = Tk.Canvas(top,height="5i",width="9i") # ,background="white")
canvas.pack() ; top.update()

# Paul Klee
mincolor,maxcolor=125,225
xmax,ymax = 800,400
stipples = [None,"gray75"] # "gray12","gray25","gray50",]
h=w=90
for x in xrange(10,xmax,w):
    for y in xrange(10,ymax,h):
        color = "#%02x%02x%02x" % (rand(mincolor,maxcolor),rand(mincolor,maxcolor),rand(mincolor,maxcolor))
        range = h/6
        stipple = stipples[rand(0,len(stipples)-1)]
        dx = r.uniform(0.0,range) - range/2
        dy = r.uniform(0.0,range) - range/2
        canvas.create_rectangle(x+dx,y+dy,x+dx+w,y+dy+h,fill=color,width=0,stipple=stipple)
#@+node:EKR.20040627150213: *5* Complex functions
import Tkinter as Tk
import random as r ; rand = r.randint

mincolor,maxcolor=125,225
xmax,ymax = 300,300
h=w=1
i = r.uniform(2.0,4.0)
j = r.uniform(-5.0,5.0)
power = r.uniform(1.1,1.2)
    #(1.5,1.7)

top = Tk.Toplevel(None)
top.title("Complex function: (%f,%f)*(x,y)**%f" % (i,j,power))
canvas = Tk.Canvas(top,height="5i",width="9i") # ,background="white")
canvas.pack() ; top.update()

@others

for n in xrange(1):
    i += 0.5
    j += 0.5
    power += 0.01
    # print "i,j,power:",i,j,power
    c = complex(i,j)
    for format,m in (
        #("#%02x%02x%02x",256),
        #("#%03x%03x%03x",256*8),
        ("#%04x%04x%04x",256*256),
    ):
        for x in xrange(0,xmax,1):
            for y in xrange(0,ymax,1):
                n = complex(x,y)
                z = pow(c*n,power)
                n1 = int(z.real*m) ; n2 = int(z.imag*m)
                color = format % (n1%m,n2%m,abs(n1-n2)%m)
                # canvas.create_rectangle(w*x,h*y,w*x+w,h*y+h,fill=color,width=0)
                canvas.create_line(x,y,x+1,y+1,fill=color,width=1)
        top.update()
print "done"
#@+node:ekr.20040319111213: *4* Tk: FilterHoist
from leoPlugins import *
from leoGlobals import *
from leoNodes import *
import Tkinter
import re
import sys

@others

fhp = None
fhp_entry = None

if 1:
    addMenu("none",None)
else:
    hooks = choose(sys.platform == 'win32',
        ('open2',"new"),
        ('start2','open2',"new"))

    print "hi"

    registerHandler(hooks,addMenu)

    __version__ = ".1"
    plugin_signon(__name__)
#@+node:ekr.20040319111213.1: *5* description
@nocolor

@ This is what it does:

1. Under Outline it puts an Option called 'FilterHoist'
2. Selecting the option pops up an ugly little window.  On it is a section where
you can type in text.  You can close the window with the close button.  You
can activate the functionality with the Filter Button.
3. Filtering will walk the Leo tree, looking for a text match from the Text
field with the Nodes bodyString.
4. After finding some nodes it creates a new node at the root.  Then it clones
the matching nodes under that new node.  A Hoist operation is performed on the
new node.  This gives a view of all matching nodes.

I put this together because I wanted a find that was based in terms of Leo's
nodes.  Find as it is bounces you around the Tree(it bothers me).  This brings
the nodes to you and presents them.  As it is I may work further on this if
people like the idea.  It's possible I might migrate it to the NodeRoundup plugin
to.

You need 2.3 python; it uses generators in it's find method.

That was one motivation for writing this thing, using a generator vs. Recursive
approach to tree walking.
#@+node:ekr.20040319142708: *5* filter
def filter(c,e):

    pat = re.compile(e.get())

    t = tnode('','A Filtered Hoist')
    newRoot = c.rootVnode().insertAfter(t)
    p = c.rootVnode()
    while p:
        if pat.search(p.b):
            clone = p.clone(p)
            clone.moveToLastChildOf(newRoot)
        p = p.threadNext()
    newRoot.moveToRoot(c.rootVnode())
    c.setCurrentVnode(newRoot)
    c.redraw()

    c.hoist()
    fhp.withdraw()
#@+node:ekr.20040319142202: *5* old code
#@+node:ekr.20040319111213.2: *6* filter
def OLDfilter(c,e):

    v = c.rootVnode()

    nodes = []
    while v:
        nodes.append(v)
        v = v.next()

    regex = re.compile(e.get())
    t = tnode('','A Filtered Hoist')
    ticker = c.rootVnode().insertAfter(t)
    for z in nodes:
        for x in search(z,regex):
            clone = x.clone( x )
            clone.moveToNthChildOf(ticker,0)
    c.setCurrentVnode(ticker)
    ticker.moveToRoot(c.rootVnode())
    c.redraw()
    c.hoist()
    fhp.withdraw()
#@+node:ekr.20040319111213.3: *6* search
from __future__ import generators # To make the code work in Python 2.2.

def OLDsearch(vn,regex):

    sn = vn 
    while vn != None:
        if regex.search( vn.b ) : yield vn
        nc = vn.numberOfChildren()
        if nc == 0:
            i = vn.childIndex()
            p = vn.parent()
            if p == None: 
                vn = None
                continue
            if i == 0:
                while 1:
                    if p == sn :
                        vn = None
                        break
                    vn = p.back() 
                    if vn == None:
                        p = p.parent()
                        continue
                    break
                continue                                                        
            vn = p.nthChild( i - 1)
            continue
        vn = vn.nthChild( nc - 1 )
#@+node:ekr.20040319111213.4: *5* filterHoist
def filterHoist(c):

    global fhp
    global e

    if fhp is None:

        fhp = Tkinter.Toplevel()
        fhp.title('FilterHoist')

        fhp_entry = e = Tkinter.Entry(fhp)
        e.pack(side="top",fill="both")

        def closeCallback(fhp=fhp):
            fhp.withdraw()

        def filterCallback(c=c,entry=e):
            filter(c,entry)

        b1 = Tkinter.Button(fhp,text='Close',command=closeCallback)
        b2 = Tkinter.Button(fhp,text='Filter',command=filterCallback)
        b1.pack(side="left")
        b2.pack(side="right")

    fhp.geometry('200x200+250+250') 
    fhp.deiconify()
    fhp_entry.focus_set()
#@+node:ekr.20040319111213.5: *5* addMenu
def addMenu(tag,keywords):

    c = top()

    trace()

    def callback(c=c):
        filterHoist(c)

    table = ("FilterHoist",None,callback),

    c.frame.menu.createMenuItemsFromTable("Outline",table)
#@+node:ekr.20150416072721.1: ** Import & export
#@+node:ekr.20121013084734.16370: *3* * Recursive import using c.recursiveImport
'''Recursively import all python files in a directory and clean the result.'''

# Latest change: use c.recursiveImport.

c.recursiveImport(
    dir_ = r'C:\prog\pyflakes-0.6.0\pyflakes',
    kind = '@clean', # The new best practice.
    one_file = False,
    safe_at_file = False,
    theTypes = None, # Same as ['.py']
)
#@+node:ekr.20130810093044.16941: *3* Export full contents
@language python

'''
From: Terry <webtourist@gmail.com>

I need to present to people who don't have leo installation, in easily
readable format, the full content of a .leo file, not just the outline, but
all nodes and all contents.

This script only exports selected nodes, so if you want to export
everything, you have to select all the top level nodes, i.e. collapse all
the nodes so only the top level is visible, click the first one, and
shift-click the last one.

It exports to plain text... although you might be able to use the
template to describe HTML, not sure.

Paste the content into a node, then click the script-button button to
create a new button for running this script. The button's name will be the
node's name, what it is doesn't matter but 'export' would be an obvious
choice. Then select the node(s) you want exported, presumably not including
the node containing the script :)

Then it will ask for a file name and whether to include unexpanded nodes.

'''

# template is everything between r""" and second """
# placeholders are H heading B body C children
# use \\n in B and C lines for conditional blank lines

template = r"""H
    B
  * C"""

lines=[]
exp_only = g.app.gui.runAskYesNoCancelDialog(
    c, 'Expanded nodes only?', 'Expanded nodes only?')
if exp_only == 'no':
    exp_only = False
    
def export_text(p, indent=''):
    
    spaces = ' '*(len(indent) - len(indent.lstrip(' ')))
    
    for line in template.split('\\n'):
        
        if 'H' in line:
            lines.append(indent + line.replace('H', p.h))
        elif 'B' in line and p.b.strip():
            prefix = line[:line.find('B')].replace('\\\\n', '\\n')
            for i in p.b.strip().split('\\n'):
                lines.append(spaces + prefix + i)
                prefix = line[:line.find('B')].replace('\\\\n', '')
            if line.endswith('\\\\n'):
                lines.append('')
        elif 'C' in line and (not exp_only or p.isExpanded()):
            prefix = line[:line.find('C')].replace('\\\\n', '\\n')
            for child in p.children():
                export_text(child, indent=spaces + prefix)
            if line.endswith('\\\\n'):
                lines.append('')
        elif 'C' not in line and 'B' not in line:
            lines.append(line)

if exp_only != 'cancel':
    for i in c.getSelectedPositions():
        export_text(i)
    
    filename = g.app.gui.runSaveFileDialog('Save to file')
    # filename = '/home/tbrown/del.txt'
    
    if filename is not None:
        open(filename,'w').write('\\n'.join(lines))
#@+node:ekr.20051103072643: *3* Export to treepad
# simple script to export current node and children as a treepad document
# the file format for treepad 2.x is simple.
# See: "TreePad 2.x File format" at http://www.treepad.com/docs/

#need to start the levels at 0
topLevel = p.level()
fileName = "exported.hjt"
nl = "\n"
mode = g.choose(c.config.output_newline=="platform",'w','wb')
try:
    theFile = open(fileName,mode)
    theFile.write("<hj-Treepad version 2.7>" + nl)
    for p in p.copy().self_and_subtree():
        theFile.write("dt=text" + nl)
        theFile.write("<node>" + nl)
        theFile.write(p.h + nl)
        theFile.write(repr(p.level() - topLevel) + nl)
        theFile.write(p.b + nl)
        theFile.write("<end node> 5P9i0s8y19Z" + nl)
    theFile.close()
    g.es("Wrote to file " + fileName,color="blue")
except IOError:
    g.es("Can not open " + fileName,color="blue")
#@+node:ekr.20041126035448: *3* Import a file
# Note: the source files contain mixed tabs/blanks, and that's very hard for Leo's imports to handle.

@tabwidth 8
@language python

path = r"c:\Python23\Lib\site-packages\Pmw\Pmw_1_1\lib\PmwPanedWidget.py"

path = r"c:\prog\PmwPanedWidget.py" # The same file with tabs converted to 8 blanks.

path = r"c:\Python23\Lib\site-packages\Pmw\Pmw_1_1\demos\All.py"

assert g.os_path_exists(path)

c.importCommands.importFilesCommand([path],"@file")
#@+node:ekr.20110929185034.15716: *3* import-org-mode
'''Import each file in the files list after the presently selected node.'''


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

@others

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

    last = root = c.p.insertAsLastChild()
    last.h = g.shortFileName(fn)
    level,stack = 0,[root]
    body = ['@others\n']
    
    for s in g.splitLines(s):
        if s.startswith('*'):
            i,level = 0,0
            while s[i] == '*':
                i += 1 ; level += 1
            if level > len(stack):
                g.trace('bad level',repr(s))
            elif level == len(stack):
                last.b = ''.join(body)
            else:
                last.b = ''.join(body)
                stack = stack[:level]
            parent = stack[-1]
            last = parent.insertAsLastChild()
            last.h = s.strip()
            stack.append(last)
            body = []
        else:
            body.append(s)
            
    # Finish any trailing lines.
    if body:
        last.b = ''.join(body)
        
    root.contract()
    c.redraw(root)
#@+node:ekr.20130802103517.20480: *3* push_to_Gist.txt
# Leo button to publish selected node as Gist.
# https://groups.google.com/forum/?fromgroups=#!topic/leo-editor/KgejcZHiEl0

import requests
import json
tmp = g.os.environ['TEMP']
description = "published from Leo"
public = True
filename = p.h      # node headline
content = p.b       # node body
g.es(filename)
print('\n\n--- %s ---' % filename)
payload = {
    'description': description,
    'public': public,
    'files': {
        filename: {'content': content}
        }
    } 
print(payload)
r = requests.post('https://api.github.com/gists',  data=json.dumps(payload))
print(r.status_code)
print(r.text)
#@+node:ekr.20110916103731.2459: *3* Recursivly create @auto nodes
import os

@others

types = ('.py',)
theDir =  r'c:\leo.repo\trunk\leo\core'

if  g.os_path_exists(theDir):
    importFiles(theDir,types,recursive=True)
    g.es("done",color="blue")
else:
    g.es("directory does not exist: " + theDir)
#@+node:ekr.20111217090057.12360: *4* cleanEmptyDirs & helper
def cleanEmptyDirs(root):
    
    '''Remove all @path nodes not containing any @auto nodes.'''
    p = root.copy()
    while p:
        if p.h.startswith('@path') and isEmpty(p):
            next = p.nodeAfterTree()
            p.doDelete()
            p = next
        else:
            p.moveToThreadNext()
#@+node:ekr.20111217090057.13163: *5* isEmpty
def isEmpty(p):
    
    for p in p.subtree():
        if p.h.startswith('@auto'):
            return False
    else:
        return True
#@+node:ekr.20111217090057.12361: *4* computeFiles
def computeFiles (theDir,recursive):
    
    '''Compute the lists of all directories and files to be added.'''
    
    dirs,files = [],[]
    for f in os.listdir(theDir):
        path = g.os_path_join(theDir,f)
        if g.os_path_isfile(path):
            name, ext = g.os_path_splitext(f)
            if not types or ext in types:
                files.append(path)
        elif recursive:
            dirs.append(path)
        
    return dirs,files
#@+node:ekr.20110916103731.2462: *4* createLastChildOf
def createLastChildOf (p,headline):

    child = p.insertAsLastChild()
    child.h = headline.replace('\\','/')
    return child
#@+node:ekr.20110916103731.2461: *4* importDir
def importDir (theDir,types,recursive,root,level=0):

    # g.es("theDir: " + theDir,color="blue")

    dirs,files = computeFiles(theDir,recursive)
    if not dirs and not files: return
    
    path_part = g.choose(level==0,theDir,g.os_path_basename(theDir))
    root = createLastChildOf(root,'@path %s' % (path_part))
    c.selectPosition(root)
    
    for fn in files:
        p2 = createLastChildOf(root,'@auto %s' % (
            g.shortFileName(fn)))
            
    # dirs.sort()
    for theDir in sorted(dirs):
        importDir(theDir,types,recursive,root,level+1)
#@+node:ekr.20110916103731.2460: *4* importFiles (top-level)
def importFiles (theDir,type=None,recursive=False):
    
    root = c.p.insertAfter()
    root.h = "imported files"
    try:
        importDir (theDir,type,recursive,root)
        cleanEmptyDirs(root)
        c.contractAllHeadlines()
        c.selectPosition(root)
        root.expand()
        for p in root.subtree():
            p.clearDirty() # Important: don't write automatically.
        c.redraw()
    except Exception:
        g.es("exception in importFiles script")
        g.es_exception()
    
#@+node:ekr.20060209173725: *3* rst-to-leo
'''A script to import rst files into Leo.

This script parses a file containing Restructured text and creates node suitable for the rst3 plugin.

To run, do the following:

1. Create a call to the function ReSt2Leo below.
   The argument to ReST2Leo should be the url (including a local file)
   of a file containing restructure text.

2. Run this node using the Execute Script command or the Run Script button.

The script will create a sibling of this node for each call to ReST2Leo in this node.
For example, this node will create a sibling called 'reStructuredText Demonstration'.
The root headline of the created tree will be the top-level heading of the imported file.
'''

@language python
@tabwidth -4

__version__ = '0.1'
<< version history >>

<< define valid_underline_characters >>

@others

# files to test with:
if 1:
    ReST2Leo('http://docutils.sourceforge.net/docs/user/rst/demo.txt')
if 0:
    ReST2Leo('http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.txt')
if 0:
    ReST2Leo('http://springfed.com/ac/IssueNo0003Info/editform')
#@+node:ekr.20060209173725.1: *4* << define valid_underline_characters >>
# all the allowable underline characters.

valid_underline_characters = [
    '!','"','#','$','%','&',"'",'(',')','*','+',
    ',','-','.','/',':',';','<','=','>','?','@',
    '[','\\',']','^','_','`','{','|','}','~',
]
#@+node:ekr.20060209175929: *4* << version history >>
@nocolor
@

v 0.1: Kent Tenney with minor mods by EKR.
#@+node:ekr.20060209173725.2: *4* class ParseReST
class ParseReST:
    """Processes a chunks of ReST, creating a list of nodes/sections
    """
    @others
#@+node:ekr.20060209173725.3: *5* __init__
def __init__(self, input):

    """Initialize document level variables
    """
    if type(input) == type('string'):
        self.lines = input.split("\n")
    else:            
        self.lines = input

    self.index = 0 

    # for each section gather title, contents and underline character
    # over-under titles are indicated by 
    # 2 character strings for underline_character
    # the initial section is root
    self.section = {'title':'root', 'contents':[], 'underline_character':'root'}
    # the list of nodes
    self.sections = []
#@+node:ekr.20060209173725.4: *5* isCharacterLine
def isCharacterLine(self):
    """Determine if the current line consists of only 
    valid underline characters
    """
    line = self.lines[self.index]
    character_line_found = False
    if len(line) > 0:
        if line[0] in valid_underline_characters:
            c = line[0]
            for char in line:
                if char == c:
                    character_line_found = True
                else:
                    character_line_found = False
                    #get out of the loop
                    #otherwise error if 1st and last are characters
                    break
    else:
        return False
    return character_line_found
#@+node:ekr.20060209173725.5: *5* isTransition
def isTransition(self):
    """self.index is pointing to a character line
    if there are blank lines on either side, this is a transition
    """

    current = self.lines[self.index]
    prev = self.lines[self.index - 1]
    next = self.lines[self.index + 1]

    return len(prev) == 0 and len(next) == 0
#@+node:ekr.20060209173725.6: *5* isUnderline
def isUnderline(self):
    """self.index is pointing to a character line 
    if we are preceded by a blank line, then a line
    not longer than this, we have an underline
    """

    current = self.lines[self.index].strip()
    prev = self.lines[self.index - 1].strip()
    prevprev = self.lines[self.index - 2].strip()


    return len(prev) > 0 and \
    len(prev) <= len(current) and \
    len(prevprev) == 0
#@+node:ekr.20060209173725.7: *5* isUnderOverline
def isUnderOverline(self):
    """self.index is pointing at a character line
    if there is a line not longer than this
    followed by a character line like this,
    we have an UnderOverline
    """

    current = self.lines[self.index].strip()
    next = self.lines[self.index + 1].strip()
    #the last line may be a character line
    try:
        nextnext = self.lines[self.index + 2]
    except IndexError:
        return False

    return (nextnext == current) and (len(next) > 0) \
    and len(next) <= len(current)
#@+node:ekr.20060209173725.8: *5* isSectionHead
def isSectionHead(self):
    """The current line is a character line,
    is this a section heading?
    http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#sections
    """
    # save typing with aliases
    current = self.lines[self.index]
    prev = self.lines[self.index - 1]
    next = self.lines[self.index + 1]

    # a transition has a blank line before and after
    if  self.isTransition():
        return False

    # underline section heading    
    if self.isUnderline():
        # previous to discovering the underline, we appended
        # the section title to the current section. 
        # Remove it before closing the section
        self.section['contents'].pop()
        self.closeCurrentSection()
        self.section['underline_character'] = current[0]
        self.section['title'] = prev
        # step index past this line
        self.index += 1
        return True

    # over-under section heading
    if self.isUnderOverline():
        self.closeCurrentSection()
        self.section['underline_character'] = current[0:2]
        # leading whitespace is allowed in over under style, remove it
        self.section['title'] = next.strip()
        # step index past overline, section title, and underline
        self.index += 3
        return True

        raise Exception ("Error in foundSectionHead()")
#@+node:ekr.20060209173725.9: *5* closeCurrentSection
def closeCurrentSection(self):
    """We have a section title, which ended the previous
    section. Add this section to nodes, and start the next
    """
    self.sections.append(self.section)
    self.section = {'title':'', 'contents':[], 'underline_character':''}
#@+node:ekr.20060209173725.10: *5* insertTitle
def insertTitle(self, uc, isSubTitle = False):
    """Inserting a title consists of merging section[1],
    the first section, into section[0], the root.
    This works the same for title and subtitle, since
    merging title deletes section[1], making the subtitle
    section[1]

    The 'isSubTitle' parameter differentiates between title and subtitle
    """    
    title = self.sections[1]['title']

    if not isSubTitle:
        self.sections[0]['title'] = title

    # extend the charline and pad the title
    charline = (len(title) * uc[0]) + (4 * uc[0])
    title = '  ' + title

    self.sections[0]['contents'].append('')
    self.sections[0]['contents'].append(charline)
    self.sections[0]['contents'].append(title)
    self.sections[0]['contents'].append(charline)
    self.sections[0]['contents'].append('')

    # append each line, not the list of lines
    for line in self.sections[1]['contents']:
        self.sections[0]['contents'].append(line)

    del self.sections[1]
#@+node:ekr.20060209173725.11: *5* fixupSections
def fixupSections(self):
    """Make corrections to the list of sections
    to reflect the syntax for 'Title' and 'Subtitle'

    If the first section heading is a unique over/under
    it is a title, and should stay in the root section.

    If the second section heading is a unique over/under
    it is a subtitle and should remain in the root section.
    """

    def isUnique(uc, start):
        index = start
        while index < len(self.sections):
            if self.sections[index]['underline_character'] == uc:
                return False
            index += 1
        return True                

    # self.sections[0] is root, a special case
    underline_first = self.sections[1]['underline_character'] 
    if len(underline_first) > 1:
        if isUnique(underline_first, 2):
            # the section head is the document title and must
            # be added to the root section
            self.insertTitle(underline_first)
    if len(self.sections) > 2:
        underline_second = self.sections[2]['underline_character'] 
        if len(underline_second) > 1:
            if isUnique(underline_second, 3):
                # the section head is the document subtitle and must
                # be added to the root section
                self.insertTitle(underline_second, True)
#@+node:ekr.20060209173725.12: *5* contents2String
def contents2String(self):
    """convert the list of strings in 
    self.sections[index]['contents'] to a string
    suitable for sticking into a Leo body
    """

    for section in self.sections:
        section['contents'] = '\n'.join(section['contents'])
#@+node:ekr.20060209173725.13: *5* processLines
def processLines(self):
    """Loop through the lines of ReST input, building a list
    of sectopms. A section consists of::
        -title
        -contents
        -underline_character
    """
    line_count = len(self.lines)

    while self.index < line_count:
        if self.isCharacterLine() and self.isSectionHead():
            # isCharacterLine() and isSectionHead() do all the housekeeping
            # required. This doesn't look like good style, but I'm not
            # sure how this should be written.
            pass
        else:        
            self.section['contents'].append(self.lines[self.index])
            self.index += 1

    self.closeCurrentSection()
    if len(self.sections) > 1:
        if len(self.sections[0]['underline_character']) > 1:
            self.fixupSections()
    self.contents2String()
    return self.sections
#@+node:ekr.20060209173725.14: *4* class BuildLeo
class BuildLeo:
    @others
#@+node:ekr.20060209173725.15: *5* __init__
def __init__(self, nodes):
    """the nodes paramater is returned by ParseReST.processLines
    It is a list of dictionaries consisting of 
    underline_character, title, contents 
    """
    self.nodes = nodes

    # self.levels is a dictionary, the keys
    # are underline_character and the value is the
    # last Leo node created at that level
    self.levels = {}

    # self.underline_characters is a list of the underline characters
    # in the order of levels. The first is always 'root'
    self.underline_characters = ['root',]




#@+node:ekr.20060209173725.16: *5* processNodes
def processNodes(self):
    """Step through the list of nodes created by
    parseReST creating the appropriate Leo nodes
    """

    # Create root node as a sibling of current node
    root = p.insertAfter()
    self.levels['root'] = root

    rootstring = self.nodes[0]['contents']
    roottitle = self.nodes[0]['title']
    root.setBodyString(rootstring)
    root.setHeadString(roottitle)

    # step through the rest of the nodes
    index = 1
    while index < len(self.nodes):
        uc = self.nodes[index]['underline_character']
        title = self.nodes[index]['title']
        contents = self.nodes[index]['contents']

        # this level exists, insert the node
        if self.levels.has_key(uc):
            # get parent of this node
            parent_index = self.underline_characters.index(uc) - 1
            parent_uc = self.underline_characters[parent_index]
            current = self.levels[parent_uc].insertAsLastChild()
            self.levels[uc] = current
            current.setHeadString(title)
            current.setBodyString(contents)

        # if this is the first time this uc is encountered
        # it means we are creating a new sublevel 
        # create the level then insert the node
        else:
            # if we are descending to a new level, the parent 
            # underline character is currently the last one
            parent = self.levels[self.underline_characters[-1] ]
            self.underline_characters.append(uc)
            current = parent.insertAsLastChild()
            self.levels[uc]  = current
            current.setHeadString(title)
            current.setBodyString(contents)

        index += 1
#@+node:ekr.20060209173725.17: *4* ReST2Leo
def ReST2Leo(input):
    """ A wrapper for ParseReST and BuildLeo
    """

    if type(input) == type(""):
        if input.startswith("http"):
            from urllib import urlopen
            try:
                data = urlopen(input).read()
            except HTTPError:
                print 'Unable to open page %s' % input
                return
        else:
            try:
                data = open(input, 'r').read()
            except IOError:
                print 'Unable to open file %s' % input
                return

    parsed = ParseReST(data)
    sections = parsed.processLines()
    nodes = BuildLeo(sections)
    nodes.processNodes()
    c.redraw()    
#@+node:ekr.20150416061426.1: ** Testing & profiling
#@+node:ekr.20061029095517: *3* * Scripts that make unit tests
#@+node:ekr.20160213075418.1: *4* @@button write-unit-tests
'''
Write all @test nodes to the test directory of the present .leo file,
under control of two templates in the TestWriter class.

First developed for the make_stub_files project.

It disables any @other directives in the @test nodes,
but leaves section references intact.
'''

# **Note**: this is a Leo script.
# It **does** have access to c, g and p.
import os
import re
@others
test_dir = os.path.dirname(c.fileName())+os.sep+'test'
assert os.path.exists(test_dir), test_dir
assert os.path.isdir(test_dir), test_dir

if 1:
    # Writes each test to a separate file in the test directory.
    TestWriter(c,path=test_dir).run(fn=None)    
if 0:
    # Writes all tests to a single file: test/unit_tests.py
    TestWriter(c,path=test_dir).run(fn='unit_tests.py')
#@+node:ekr.20160213075418.3: *5* class TestWriter


class TestWriter:
    
    << define file_template >>
    << define test_template >>

    @others
#@+node:ekr.20160213075418.4: *6* << define file_template >>
# Add any other common imports here.

file_template = '''\
import unittest
from make_stub_files import *
'''

file_template = g.adjustTripleString(file_template,c.tab_width)
#@+node:ekr.20160213075418.5: *6* << define test_template >>
test_template = '''
class %s (unittest.TestCase):
    def runTest(self):
%s
'''

test_template = g.adjustTripleString(test_template,c.tab_width)
#@+node:ekr.20160213075418.6: *6*  ctor

def __init__(self,c,path=''):
    '''TestWriter ctor.'''
    self.c = c
    load_dir = g.os_path_dirname(c.fileName())
    self.path = g.os_path_finalize_join(load_dir,path)
    self.nodes = []
    assert g.os_path_exists(self.path),self.path
#@+node:ekr.20160213075418.7: *6* clean

def clean(self,s):
    '''Munge s so that it can be used as a file name.'''
    result,tag = [],'@test'
    if s.startswith(tag):
        s = s[len(tag):]
    for ch in s.strip():
        if ch.isalnum():
            result.append(ch)
        else:
            result.append('_')
        # elif ch.isspace():
            # result.append('_')
    s = ''.join(result)
    if s.endswith('.py'):
        s = s[:-3]
    if not s.startswith('test'):
        s = 'test_' + s
    return s.replace('__','_').strip()
#@+node:ekr.20160213075418.8: *6* get_body

def get_body(self, p):
    '''Convert p.b to a valid script.'''
    s_old = p.b
    # Suppress @others but not section references.
    p.b = p.b.replace('@others', '')
    assert p.b.find('@others') == -1
    s = g.getScript(c, p,
                    useSelectedText=False,
                    forcePythonSentinels=True,
                    useSentinels=False)
    p.b = s_old
    s = ''.join([' '*8+z for z in g.splitLines(s) if z.strip()])
            # Add leading indentation needed by test_template.
    return s.rstrip()+'\n'
#@+node:ekr.20160213075418.9: *6* run

def run(self,fn=None):
    
    n, p = 0, c.rootPosition()
    while p:
        if p.h.startswith('@ignore '):
            p.moveToNodeAfterTree()
        elif p.h.startswith('@test '):
            self.nodes.append(p.copy())
            if not fn:
                fn2 = self.clean(p.h)+'.py'
                self.write_file(fn2)
                self.test(fn2)
                self.nodes=[]
            n += 1
            p.moveToThreadNext()
        else:
            p.moveToThreadNext()
    if n == 0:
        print('no @file or @suite nodes found.')
    else:
        if fn:
            self.write_file(fn)
            self.test(fn)
        dest = g.os_path_join(self.path, fn) if fn else self.path
        print('wrote %s test%s to %s' % (n,self.plural(n), dest))
#@+node:ekr.20160213075418.10: *6* plural

def plural(self, n):
    return 's' if n > 1 else ''
#@+node:ekr.20160213075418.11: *6* test

def test(self,fn):
    '''Test the newly created file.'''
    import imp
    import sys

    if self.path not in sys.path:
        sys.path.append(self.path)
    assert fn.endswith('.py')
    name = fn[:-3]
    try:
        f,path,desc = imp.find_module(name,[self.path])
        imp.load_module(name,f,path,desc)
        # print('imported %s' % (name))
    except Exception:
        print('can not import: %s' % (name))
        g.es_print_exception()
#@+node:ekr.20160213075418.12: *6* write_file

def write_file(self,fn):

    assert g.os_path_exists(self.path),self.path
    fn = g.os_path_finalize_join(self.path,fn)
    f = open(fn,'w')
    f.write(self.file_template)
    # g.trace(''.join([z.h for z in self.nodes]))
    for p in self.nodes:
        f.write(self.test_template % (self.clean(p.h),self.get_body(p)))
    f.close()
    g.trace('wrote', fn)
#@+node:ekr.20061029095517.1: *4* @command make-test
p1 = p.insertAfter()
c.setHeadString(p1,'@test ')
body = 'import leoTest\nleoTest.runEditCommandTest(c,p)'
c.setBodyString(p1,body)
for s in ('work','before','after'):
    p2 = p1.insertAsLastChild()
    c.setHeadString(p2,s)
p1.expand()
c.redraw()
c.editPosition(p1)
#@+node:ekr.20061029095517.2: *4* @command do-before
@
p should be in tree whose root is a @test node containing 'work', 'before' and
'after' children. The work node should have body text. If all is as expected,
copy the body text the work node to the before node, and represent the selection
range of the work in the headline of the before node.
@c

@others

sel = getSel(c)
top,work,before,after = findNodes(p)
if top and work.b:
    c.setBodyString(before,work.b)
    c.setBodyString(after,'')
    putSelectionInHeadline(c,before,'before',sel)
    c.redraw()
else:
    g.es_print('do-before: not in a proper @test tree')
#@+node:ekr.20061029095517.3: *5* getSel
def getSel(c):

    w = c.frame.body.bodyCtrl
    sel = g.app.gui.getSelectionRange(w)
    if not sel:
        i = g.app.gui.getInsertPoint(w)
        sel = (i,i)
    return sel
#@+node:ekr.20061029095517.4: *5* findNodes
def findNodes(p):

    '''Find the top, work, before and after nodes.
    p should be in tree whose root is a @test node containing
    'work', 'before' and 'after' children.'''

    for p in p.self_and_parents():
        if p.h.startswith('@test '):
            break
    top    = p and p.copy()
    work   = top and top.firstChild() 
    before = work and work.next()     
    after  = before and before.next()
    if (
        work   and work.h.startswith('work') and
        before and before.h.startswith('before') and
        after  and after.h.startswith('after')
    ):
        return top,work,before,after
    else:
        return None,None,None,None
#@+node:ekr.20061029095517.5: *5* putSelectionInHeadline
def putSelectionInHeadline (c,p,prefix,sel):

    # g.trace(p.h,repr(sel))

    if not sel:
        sel = ('1.0','1.0')

    s = '%s sel=%s,%s' % (prefix,sel[0],sel[1])

    c.setHeadString(p,s)

#@+node:ekr.20061029095517.6: *4* @command do-after
@
p should be in tree whose root is a @test node containing 'work', 'before' and
'after' children. If all is as expected, copy the work node to the after node,
and represent the selection range of the work node in the headline of the after node.
@c

@others

sel = getSel(c)
top,work,before,after = findNodes(p)
if top:
    c.setBodyString(after,work.b)
    putSelectionInHeadline(c,after,'after',sel)
    c.redraw()
else:
    g.es_print('do-after: not in @test tree')
#@+node:ekr.20061029095517.7: *5* getSel
def getSel(c):

    w = c.frame.body.bodyCtrl
    sel = g.app.gui.getSelectionRange(w)
    if not sel:
        i = g.app.gui.getInsertPoint(w)
        sel = (i,i)
    return sel
#@+node:ekr.20061029095517.8: *5* findNodes
def findNodes(p):

    '''Find the top, work, before and after nodes.
    p should be in tree whose root is a @test node containing
    'work', 'before' and 'after' children.'''

    for p in p.self_and_parents():
        if p.h.startswith('@test '):
            break
    top    = p and p.copy()
    work   = top and top.firstChild()
    before = work and work.next()
    after  = before and before.next()
    if (
        work   and work.h.startswith('work') and
        before and before.h.startswith('before') and
        after  and after.h.startswith('after')
    ):
        return top,work,before,after
    else:
        return None,None,None,None
#@+node:ekr.20061029095517.9: *5* putSelectionInHeadline
def putSelectionInHeadline (c,p,prefix,sel):

    # g.trace(p.h,repr(sel))

    if not sel:
        sel = ('1.0','1.0')

    s = '%s sel=%s,%s' % (prefix,sel[0],sel[1])

    c.setHeadString(p,s)

#@+node:ekr.20061029095517.10: *4* @test kill-line
import leoTest
leoTest.runEditCommandTest(c,p)
#@+node:ekr.20061029095517.11: *5* work
line 1
line 3
#@+node:ekr.20061029095517.12: *5* before sel=2.0,2.6
line 1
line 2
line 3
#@+node:ekr.20061029095517.13: *5* after sel=2.0,2.0
line 1
line 3
#@+node:ekr.20150416171508.1: *3* @ignore testing buttons
#@+node:ekr.20040721113934: *4* @button Run the profiler on script in c.p.b
import profile
import pstats

# Note: the profiled code should do all needed imports.
path = g.os_path_abspath(g.os_path_join(g.app.loadDir,'..','test','leoProfile.txt'))
path = str(path)

if p.b.rstrip():
    s = p.b.rstrip() + '\n'
    profile.run(s,path)
    print '-' * 40
    print "Profiling info sent to %s" % path
    stats = pstats.Stats(path)
    stats.strip_dirs()
    stats.sort_stats('cum','file','name')
    stats.print_stats()
#@+node:ekr.20040901065642.2: *4* @button Run timit on script in c.p.b
@ Improved timeit script after an idea by 'e'.

Comments of the form #@count nnn set the repeat count.
Comments of the form #@setup comment delimits the end of setup code.
@c

try:
    import timeit # Exists only in Python 2.3 and above.
except ImportError: 
    timeit = None
    print "Can not import timeit"

if timeit and p.b.strip():
    s = p.b.rstrip() + '\n'
    << scan for #@count >>
    << put setup code in s1 and everything else in s2 >>
    t = timeit.Timer(stmt=s2,setup=s1)
    try:
        if 1: # faster.
            result = t.timeit(count)
        else: # better results.
            result = min(t.repeat(3, count))  
        print "count: %d : %f %s" % (
            count, result, p.h.strip())
    except:
        t.print_exc()
#@+node:ekr.20040901072339: *5* << scan for #@count >>
lines = s.split('\n')

count = 1000000 # default count
tag = "#@count"

for line in lines:
    i = g.skip_ws(line,0)
    if g.match(line,0,tag):
        i += len(tag)
        i = g.skip_ws(line,i)
        junk,val = g.skip_long(line,i)
        if val is not None:
            count = abs(val)
            # print "Setting count to",count
            break
#@+node:ekr.20040901071028: *5* << put setup code in s1 and everything else in s2 >>
lines = s.split('\n')

for i in xrange(len(lines)):
    if lines[i].strip() == "#@setup":
        break

if i < len(lines):
    # Split at the #@setup line and delete the #@setup line
    s1 = '\n'.join(lines[:i])
    s2 = '\n'.join(lines[i:])
    #print "setup",repr(s1)
    #print "code",repr(s2)
else:
    # There is no setup.
    s1 = None
    s2 = s

if not s1: s1 = 'pass'
if not s2: s2 = 'pass'
#@+node:ekr.20080329083943.1: *4* @button Write cleaned files to 'clean' directory
# Writes all derived files in the outline to 'clean' directories.
# For example, writes test/foo.py to test/clean/foo.py.

@others

for p in c.all_positions():
    if p.isAnyAtFileNode():
        clean_file(p)
#@+node:ekr.20080329083943.2: *5* clean_file
def clean_file(p):

    at = c.atFileCommands

    if hasattr(p.v,'tnodeList'):
        has_list = True
        old_list =  p.v.tnodeList[:]
    else:
        has_list = False

    at.write(
        root=p,nosentinels=True,
        thinFile=False,
        scriptWrite=True,
        toString=True,
        write_strips_blank_lines=False)

    if has_list:
        p.v.tnodeList = old_list

    fileName = g.os_path_normpath(g.os_path_join(
        at.default_directory,
        'clean',
        g.os_path_basename(p.anyAtFileNodeName())))

    # g.trace(p.h,len(at.stringOutput),fileName)

    # Adapted from at.openFileForWritingHelper
    path = g.os_path_dirname(fileName)
    if not g.os_path_exists(path):
        g.es('clean directory does not exist',path)
        return

    try:
        f = file(fileName,'w')
        f.write(at.stringOutput)
        f.close()
        g.es_print('wrote',fileName)
    except IOError:
        g.es_print('can not write',fileName,color='red')
        g.es_exception()
#@+node:ekr.20121220111212.13257: *4* @button write-unit-tests
'''
<< about this script >>
'''

@others

# Change test_dir as desired: it must already exist.
# test_dir = g.os_path_finalize_join(g.app.loadDir,'..','test')
test_dir = os.path.dirname(c.fileName())+os.sep+'test'

if 1:
    # Writes each test to a separate file in the test directory.
    TestWriter(c,path=test_dir).run(fn=None)    
else:
    # Writes all tests to a single file: test/unit_tests.py
    TestWriter(c,path=test_dir).run(fn='unit_tests.py')
#@+node:ekr.20121220111212.13258: *5* << about this script >>
@language rest

This script transliterates @test nodes into .py file. The two main ways of
using this script are as follows::

    TestWriter(c,path='test').run(fn='unit_tests.py') # writes one file
    TestWriter(c,path='test').run(fn=None)            # writes separate files.
     
The first writes all tests to test/unit_tests.py; the second writes each
unit test to a separate .py file in the test directory.

The script imports each written file and reports any syntax errors.

This is a straightforward script; it should be easy to modify it to suit
individual needs.

The <\< file_template >> and <\< test_template >> sections in the TestWriter
class determines exactly what this script writes.
#@+node:ekr.20121220111212.13259: *5* class TestWriter
class TestWriter:
    
    << define file_template >>
    << define test_template >>

    @others
#@+node:ekr.20121220111212.13260: *6* << define file_template >>
# Add any other common imports here.

file_template = '''\
import unittest
'''

file_template = g.adjustTripleString(file_template,c.tab_width)
#@+node:ekr.20121220111212.13261: *6* << define test_template >>
test_template = '''
class %s (unittest.TestCase):
    def runTest(self):
%s
'''

test_template = g.adjustTripleString(test_template,c.tab_width)
#@+node:ekr.20121220111212.13262: *6*  ctor
def __init__(self,c,path=''):
    
    self.c = c
    load_dir = g.os_path_dirname(c.fileName())
    self.path = g.os_path_finalize_join(load_dir,path)
    self.nodes = []
    assert g.os_path_exists(self.path),self.path
#@+node:ekr.20121220111212.13263: *6* clean
def clean(self,s):
    
    '''Munge s so that it can be used as a file name.'''
    
    result,tag = [],'@test'
    if s.startswith(tag):
        s = s[len(tag):]
    for ch in s.strip():
        if ch.isalnum():
            result.append(ch)
        elif ch.isspace():
            result.append('_')
    s = ''.join(result)
    if s.endswith('.py'):
        s = s[:-3]
    return s.strip()
#@+node:ekr.20121220111212.13264: *6* get_body
def get_body(self,p):
    
    '''Convert p.b to a valid script.'''
    
    s = g.getScript(c,p,
        useSelectedText=False,forcePythonSentinels=True,useSentinels=False)

    s = ''.join([' '*8+z for z in g.splitLines(s) if z.strip()])
    
    return s.rstrip()
#@+node:ekr.20121220111212.13265: *6* run
def run(self,fn=None):
    n,p = 1,c.rootPosition()
    while p:
        if p.h.startswith('@ignore '):
            p.moveToNodeAfterTree()
        elif p.h.startswith('@test '):
            self.nodes.append(p.copy())
            if not fn:
                fn2 = self.clean(p.h)+'.py'
                self.write_file(fn2)
                self.test(fn2)
                self.nodes=[]
            n += 1
            p.moveToThreadNext()
        else:
            p.moveToThreadNext()
    if fn:
        self.write_file(fn)
        self.test(fn)
    dest = g.os_path_join(self.path,fn) if fn else self.path
    print('wrote %s tests to %s' % (n,dest))
#@+node:ekr.20121220111212.13266: *6* test
def test(self,fn):
    
    '''Test the newly created file.'''
    
    import imp
    import sys

    if self.path not in sys.path:
        sys.path.append(self.path)

    assert fn.endswith('.py')
    name = fn[:-3]
    try:
        f,path,desc = imp.find_module(name,[self.path])
        imp.load_module(name,f,path,desc)
        # print('imported %s' % (name))
    except Exception:
        print('can not import: %s' % (name))
        g.es_print_exception()
#@+node:ekr.20121220111212.13267: *6* write_file
def write_file(self,fn):

    assert g.os_path_exists(self.path),self.path
    fn = g.os_path_finalize_join(self.path,fn)
    f = open(fn,'w')
    f.write(self.file_template)
    for p in self.nodes:
        f.write(self.test_template % (self.clean(p.h),self.get_body(p)))
    f.close()
#@+node:ekr.20060911090501: *3* Check the big reorg
'''
A script to check to see all changes required by the 'big reorg' got
done properly.
'''

<< define data >>
<< define regex patterns >>

@others

g.es_print('='*40)

g.es_print('%s %s' % ('-'*20,'must have a c arg'))
for p in c.all_positions():
    findFunctionCalls(add_c_arg,p,showEmptyArgs=True)

g.es_print('%s %s' % ('-'*20,'these methods should not exist'))
for p in c.all_positions():
    findFunctionCalls(removed,p,showEmptyArgs=True)

g.es_print('%s %s' % ('-'*20,'p methods should have no c arg'))
for p in c.all_positions():
    findFunctionCalls(p_methods,p)

g.es_print('%s %s' % ('-'*20,'v methods should have no c arg'))
for p in c.all_positions():
    findFunctionCalls(v_methods,p)

g.es_print('%s %s' % ('-'*20,'c methods should start with a position arg'))
for p in c.all_positions():
    findFunctionCalls(c_methods,p)

g.es_print('done')
#@+node:ekr.20060911090501.1: *4* << define data >>
removed = (
    'p.allNodes_iter',
    'p.all_positions_iter',
    'p.appendStringToBody',
    'p.edit_widget',
    'p.lastVisible',
    'p.positionExists',
    'p.setBodyString',
    'p.trimTrailingLines',
    'p.setHeadString',
    'p.setBodyStringOrPane',
    'v.setBodyStringOrPane',
    'c.setBodyStringOrPane',
    'p.setBodyTextOrPane',
    'v.setBodyTextOrPane',
    'c.setBodyTextOrPane',
    'p.setHeadStringOrPane',
    'v.setHeadStringOrPane',
    'c.setHeadStringOrPane',
    'p.setHeadTextOrPane',
    'v.setHeadTextOrPane',
    'c.setHeadTextOrPane',
    #'p.clearMarked',
    #'p.initHeadString',
    #'p.setMarked',
)

add_c_arg = ( # Must have a c arg.
    'g.findReference',
)

p_methods = (  # Should not have c arg.
    'p.clone',
    'p.doDelete',
    'p.linkAsRoot',
    'p.moveAfter'
    'p.moveToLastChildOf',
    'p.moveToNthChildOf',
    'p.moveToRoot',
    'p.setDirty',
    'p.unlink',
)

v_methods = ( # Should not have c arg.
    'v.linkAsRoot',
    'v.moveToRoot',
    'v.unlink',
)

c_methods = (
    'c.appendStringToBody',
    'c.clearMarked',
    'c.edit_widget',
    'c.initHeadString',
    'c.lastVisible',
    'c.positionExists',
    'c.setBodyString',
    'c.setMarked',
    'c.trimTrailingLines',
    'c.setHeadString',
)
#@+node:ekr.20060911090501.2: *4* << define regex patterns >>
import re

# Allow . in word.
function_call_re = re.compile(r'([a-zA-Z_][a-zA-Z_0-9.]*)\s*(\(.*\))')
#@+node:ekr.20060911090501.3: *4* findFunctionCalls
def findFunctionCalls (names,p,showEmptyArgs=False):

    '''Find all call to function f in node p.

    Return a list of tuples (s0, s1, s0) where:

    - s0 is entire match.
    - s1 is the function name.
    - s2 is the argument list.'''

    result = [] ; start = 0 ; body = p.b
    while start < len(body):
        mo = function_call_re.search(body,start)
        if not mo: return result
        end = mo.end()
        s0 = mo.group(0) or ''
        s1 = mo.group(1) or ''
        s2 = mo.group(2) or ''
        s2 = s2.strip()
        args = s2[1:-1].strip()
        if s1 in names:
            if args or showEmptyArgs: # or not args.startswith('self'):
                g.es_print('%20s (%s)' % (s1,args))
                result.append((s0,s1,s2),)
        assert(end > start)
        start = end
#@+node:ekr.20161017063308.1: *3* Find problems in pylint-leo-rc.txt
g.cls()
print_all_lines = True
table = [
    r'c:\leo.repo\leo-editor\leo\test\pylint-leo-rc.txt',
    r'c:\leo.repo\leo-editor\leo\test\pylint-leo-rc-ref.txt',
]
for path in table:
    f = open(path)
    s = f.read()
    f.close()
    # print(len(s))
    errors = []
    n = 0
    for i, line in enumerate(g.splitLines(s)):
        try:
            if print_all_lines:
                print('  %3s %4s %s' % (i+1, n, line.rstrip()))
            else:
                g.toUnicode(line)
        except UnicodeEncodeError:
            print('**%3s %4s %s' % (i+1, n, len(line.rstrip())))
            errors.append(i+1)
        n += len(line)
    print('%s error lines: %s' % (g.shortFileName(path), errors))
#@+node:ekr.20040721145258.1: *3* Run leoTest.runGc
import leoTest

leoTest.runGc(disable=True)
#@+node:ekr.20051204180404: ** Text editing
#@+node:ekr.20150416171056.1: *3* @ignore text editing buttons
#@+node:ekr.20051213093427: *4* @button Add tab after each :
@color

s = p.b

result = []
lines = g.splitLines(s)
for line in lines:
    i = line.find(':')
    if i > -1:
        result.append(line[:i+1] + ' '*4 + line[i+1:])
    else:
        result.append(line)

s = ''.join(result)
g.trace(s)
c.setBodyString(p,s)
#@+node:ekr.20051213092601: *4* @button Add tab before each =
@color

s = p.b
result = []
lines = g.splitLines(s)
for line in lines:
    i = line.find('=')
    if i > -1:
        result.append(line[:i] + ' '*4 + line[i:])
    else:
        result.append(line)
s = ''.join(result)
g.trace(s)
# c.setBodyString(p,s)
#@+node:ekr.20040723065021: *4* @button Call g.stripBlankLines on a subtree
# Tag the start of the command.
u = c.undoer
u.beforeChangeGroup(c.p,"Change All")
n = 0 ; total = 0
for p in c.p.self_and_subtree():
    total += 1
    body = p.bodyString() # Don't use p.b
    s = g.stripBlankLines(body)
    if s != body:
        n += 1
        c.setBodyString(p,s)
        u.setUndoTypingParams(p,'Change',
            oldText=body,newText=s,oldSel=None, newSel=None)

# Tag the end of the command.
u.afterChangeGroup(c.p,"Change All",reportFlag=False,dirtyVnodeList=[])
print("%d nodes changed (%d total)" % (n,total))
#@+node:ekr.20040723065047: *5* Test
@ignore
a
  b

c  

last
@language python
#@+node:ekr.20080519162425.1: *4* @button Change leo imports
change = False # True: actually make the changes.
trace = True

print '-' * 40

tag = 'import leo' ; n = len('import ')

for p in p.self_and_subtree():
    result = []
    for s in g.splitlines(p.b):
        i = s.find(tag,0)
        if i > -1:
            # Do nothing if we have already done the translation.
            i2 = s.find('<<') ; i3 = s.find('import leo.core')
            if i2 == -1 and i3 == -1:
                i += n
                j = g.skip_c_id(s,i)
                word = s[i:j]
                rest = s[j:]
                if rest.strip().startswith('as'):
                    s = s[:i] + 'leo.core.' + word + rest
                else:
                    s = s[:i] + 'leo.core.' + word + ' as ' + word + rest
                if trace:
                    print p.h
                    j,k=g.getLine(s,i) ; print s[j:k]
        result.append(s)
    result = ''.join(result)

    if change and result != p.b:
        c.setBodyString(p,result)

#@+node:ekr.20051110105027.149: *4* @button Change OnX to x in headline
# Change OnXxx to xxx in all headlines & body text.
for p in p.self_and_subtree():
    # Headlines.
    h = p.h
    if g.match(h,0,"On") and len(h) > 2:
        h = h[2].lower() + h[3:]
        print(h)
        p.h = h
    # Body text
    s = p.b
    if s:
        i = s.find("def ")
        if i > -1:
            c = s[i+6].lower()
            s = s[:i] + "def " + c + s[i+7:]
            print(p.h)
            p.b = s
#@+node:ekr.20071213062051: *4* @button Create headlines from body
# Create child nodes whose headline are all lines in the body.
s = p.b
lines = g.splitLines(s)
lines = [z.strip() for z in lines if z.strip()]

for line in lines:
    p2 = p.insertAsLastChild()
    while line.find('  ') > -1:
        line = line.replace('  ',' ')
    i = line.find('#')
    if i == -1:
        p2.initHeadString(line)
    else:
        # Put comments in the body.
        p2.initHeadString(line[:i].strip())
        c.setBodyString(p2,line[i:].strip())
c.redraw()
#@+node:ekr.20051204180404.1: *4* @button Delete from ':' to end (script)
@color

s = p.b
result = []
lines = g.splitLines(s)
for line in lines:
    i = line.find(':')
    if i > -1:
        result.append(line[:i])
        if line and line[-1] == '\n':
            result.append('\n')
    else:
        result.append(line)

s = ''.join(result)
c.setBodyString(p,s)
#@+node:ekr.20060303080421: *4* @button Delete from first blank
@color

s = p.b
result = []
lines = g.splitLines(s)
for line in lines:
    i = line.find(' ')
    if i > -1:
        result.append(line[:i])
        if line and line[-1] == '\n':
            result.append('\n')
    else:
        result.append(line)

s = ''.join(result)
c.setBodyString(p,s)
#@+node:ekr.20060813102424: *4* @button Remove blank trailing lines
for p in p.self_and_subtree():
    s = p.b
    target = s.rstrip()
    if s != target:
        c.setBodyString(p,target)
        g.es(p.h)
#@+node:ekr.20051218212007: *4* @button Replace =.* with = None
@color

s = p.b
result = []
lines = g.splitLines(s)
for line in lines:
    i = line.find('=')
    if i > -1:
        result.append(line[:i] + '= None')
        if line and line[-1] == '\n':
            result.append('\n')
    else:
        result.append(line)

s = ''.join(result)
c.setBodyString(p,s)
#@+node:ekr.20140426052603.18106: *4* @button Replace g.choose
'''Replace "g.choose(test,a,b)" by "a if test else b".'''

replace = False # True: make the actual replacements

class Controller:
    @others

if 0: # Testing convenience...
    g.cls()
    c.save()
    p = g.findNodeAnywhere(c,'Code')
    assert(p)
Controller().run(c,p,replace)
#@+node:ekr.20140426052603.18107: *5* run
def run(self,c,p,replace):
    '''Main line for undoable replace g.choose with ternary operator.'''
    changed,n,found,p1 = 0,0,0,p.copy()
    dirtyVnodeList,tag,u = [],'replace g.choose',c.undoer
    u.beforeChangeGroup(p1,tag)
    for p in p.self_and_subtree():
        s,i = p.b,0
        n += 1
        while i < len(s):
            progress = i
            i,j = self.find(s,i)
            if i == -1: break
            found += 1
            s,i = self.replace(s,i,j)
            assert progress < i
        if replace and p.b != s:
            changed += 1 
            b = c.undoer.beforeChangeNodeContents(p)
            dirtyVnodeList2 = p.setDirty()
            dirtyVnodeList.extend(dirtyVnodeList2)
            p.b = s
            c.undoer.afterChangeNodeContents(p,tag,b)
    u.afterChangeGroup(p1,tag,dirtyVnodeList=dirtyVnodeList)
    print('scanned %s nodes found %s changed: %s' % (n,found,changed))
#@+node:ekr.20140426052603.18108: *5* find
def find(self,s,i):
    '''
    Return (n1,n2) such that s[n1:n2] is the next choose(cond,a,b) in s[i:].
    Return -1,-1 if there no more are found.
    '''
    while i < len(s):
        progress = i
        ch = s[i]
        if ch in '"\'':
            i = g.skip_python_string(s,i,verbose=False)
        elif ch == '#':
            i = g.skip_line(s,i) # This eats the comment!
        # elif ch == '(':
            # i = g.skip_matching_python_delims(s,i,'(',')')
            # if i > -1: assert s[i] == ')'
        else:
            for tag in ('g.choose(',): # 'choose('):
                if s[i:].startswith(tag):
                    # g.trace(s[i:i+20].strip())
                    i1 = i
                    i += len(tag)-1
                    i = g.skip_matching_python_delims(s,i,'(',')')
                    if i > -1:
                        assert s[i] == ')',repr(s[i:i+20])
                        return i1,i+1
            else:
                i += 1
        if i == -1: i = progress + 1
        assert progress < i,(i,s,ch)
    return -1,-1
#@+node:ekr.20140426052603.18109: *5* get_arg
def get_arg(self,s,i):
    '''return j such that s[i:j] is the argument.'''
    assert s[i] in '(,',repr(s[i:i+10])
    i += 1
    result = []
    while i < len(s):
        progress = i
        ch = s[i]
        if ch in ',)':
            break
        if ch in ' \t\n':
            # Don't put leading ws, and don't duplicate ws.
            if len(result) > 0 and result[-1] != ' ':
                result.append(' ')
            i += 1
        elif ch in '"\'':
            j = g.skip_python_string(s,i,verbose=False)
            if j > -1:
                result.append(s[i:j])
                i = j
            else:
                i += 1
        elif ch == '#':
            i = g.skip_line(s,i) # This eats the comment.
        elif ch == '(':
            j = g.skip_matching_python_delims(s,i,'(',')')
            assert s[j] == ')',s[j:j+10]
            result.append(s[i:j+1])
            i = j+1
        else:
            result.append(ch)
            i += 1
        assert progress < i
    assert s[i] in ',)'
    s = ''.join(result)
    return s,i
#@+node:ekr.20140426052603.18110: *5* munge
def munge(self,s):
    '''Compute the ternary operator corresponding to s.'''
    g.trace('1:',s)
    for tag in ('g.choose',): # 'choose'):
        if s.startswith(tag):
            i = len(tag)
            break
    else: assert False
    assert s[i] == '('
    args = []
    for n in (0,1,2):
        arg,i = self.get_arg(s,i)
        assert arg
        args.append(arg)
    arg1,arg2,arg3 = args
    result = '%s if %s else %s' % (arg2,arg1,arg3)
    g.trace('2:',result)
    return result
#@+node:ekr.20140426052603.18111: *5* replace
def replace(self,s,i,j):
    '''Replace s[i:j] containing a g.trace, with the corresponding ternaty operator.'''
    s1 = s[i:j]
    s2 = self.munge(s1)
    if s1 == s2:
        return s,j
    else:
        s3 = s[:i] + s2 + s[j:]
        j = i + len(s2)
        return s3,j
#@+node:ekr.20060808103945: *4* @button Set trailing ws
'''
This script quickly ends all nodes in the selected tree with exactly one
newline and marks all @thin/@file nodes dirty if any of their descendents have
been changed.
'''

@others

p = c.currentPosition()
pass1(p) # Make the changes and do p.v.setDirty for all changed nodes p.
pass2() # Quickly set all @thin/@file nodes dirty if any of their descendents are dirty.
g.es_print('done')
c.redraw()
#@+node:ekr.20060808103945.1: *5* pass1
def pass1(root):

    '''Remove trailing newlines from all nodes.'''

    count = 0 ; seen = {}
    for p in root.self_and_subtree():
        if seen.get(p.v): continue
        s = p.b
        if s:
            s2 = s.rstrip() + '\n'
            if s2 != s:
                s2 = g.toUnicode(s2,g.app.tkEncoding,reportErrors=True)
                p.v._bodyString = s2
                seen [p.v] = True
                p.v.setDirty() # Just set the bit: do **not** redraw!
                count += 1

    g.es_print("pass 1: %d nodes converted" % count)
#@+node:ekr.20060808103945.2: *5* pass2
def pass2():

    '''Quickly mark all changed @file nodes dirty.'''

    count = 0
    for p in c.all_unique_positions():
        if p.isAnyAtFileNode():
            root = p.copy()
            for p2 in root.self_and_subtree():
                if p2.v.isDirty():
                    root.setDirty()
                    count += 1
                    break

    g.es_print("pass 2: %d @file/@thin nodes set dirty" % count)
#@+node:ekr.20071001114854: *4* @button Split defs in body text to child nodes
# The parse-body command now does this.
# script to split node containing multiple def's into child nodes

def createChild (parent,body,line):
    p = parent.insertAsLastChild()
    h = line [3:].strip()
    i = h.find('(')
    if i > -1:
        func = h [: i].strip()
    h2 = g.choose(func,func,h)
    p.setHeadString(h2)
    p.setTnodeText(''.join(body))

s = p.b
if s.strip():
    b = c.undoer.beforeChangeTree(p)
    lines = g.splitLines(s) ; body = [] ; changed = False ; lastDefLine = ''
    for line in lines:
        if g.match_word(line,0,'def'):
            if body and lastDefLine:
                createChild(p,body,lastDefLine)
                body = [] ; changed = True
            lastDefLine = line
        body.append(line)
    if body and lastDefLine:
        createChild(p,body,lastDefLine)
        changed = True
    if changed:
        c.setChanged(True) ; c.setBodyString(p,'')
    c.undoer.afterChangeTree(p,'split-defs',b)
    c.redraw()
#@+node:ekr.20160302141032.1: *3* Commands that support Microsoft outlook format
# support Windows 'outlook:' protocol in URLs
# By Brian Theado and Jon N.

https://groups.google.com/forum/#!searchin/leo-editor/ability$20to$20support$20Windows$20$27outlook$3A$27$20protocol$20in$20URLs$3F/leo-editor/vIaqh0DXbek/Y1AINopXDAAJ
#@+node:ekr.20160302141032.2: *4* @@command insert-outlook-email-link
'''
Copies outlook link of current selected email into the body at the current
insertion point.
'''

import win32com.client

ol = win32com.client.Dispatch("Outlook.Application")
m = ol.ActiveExplorer().Selection.Item(1)
u = "outlook:%s <MESSAGE: %s>" % (m.EntryID, m.Subject)

w = c.frame.body.wrapper
i = w.getInsertPoint()
w.insert(i, u)

@language python



#@+node:ekr.20160302141032.3: *4* @@command open-selected-outlook-link
'''Launches the outlook url (the url text must be selected).'''
import os
w = c.frame.body.wrapper
r = w.getSelectionRange()
url = w.get(r[0], r[1])
os.startfile(url, 'open')
#@+node:ekr.20160923133728.1: *3* Remove tabs from leo/modes/*.py
@language python

import glob
path = g.os_path_join(g.app.loadDir, '..', 'modes', '*.py')
# print(g.os_path_exists(path))
aList = glob.glob(path)
# print('\n'.join(sorted(aList)))
for fn in aList:
    f = open(fn, 'r')
    s1 = f.read()
    f.close()
    s2 = s1.replace('\t', '    ')
    if s1 != s2:
        print('changed: %s' % fn)
        f = open(fn, 'w')
        f.write(s2)
        f.close()
#@+node:ekr.20141105055521.16: *3* Replace body.x with body.wrapper.x
@language python

'''
A script to replace body.x with body.wrapper.x for all x in the WrapperAPI.

It is *not undoable* to save massive amounts of memory.
Please run on an already-saved .leo file, and take all other
reasonable precautions.

If replace is False, it will just report the changes to be made.
'''
import leo.core.leoFrame as leoFrame
replace = False
aList = sorted([z for z in dir(leoFrame.WrapperAPI) if not z.startswith('__')])
nodes = 0
for p in c.all_unique_positions():
    s = p.b
    nodes += 1
    found = False
    for target in aList:
        i = 0
        pattern = 'body.' + target
        while True:
            i = s.find(pattern,i)
            if i == -1:
                break
            if g.match_word(s,i,pattern):
                if not found:
                    print('In node: %s' % p.h)
                    found = True
                i1,i2 = g.getLine(s,i)
                if replace:
                    j = i + len('body.')
                    s = s[:j] + 'wrapper.' + s[j:]
                    print(s[i1:i2+len('wrapper.')].rstrip())
                    i += len('wrapper.') + len(pattern)
                else:
                    print(s[i1:i2].rstrip())
                    i += len(pattern)
            else:
                i += len(pattern)
    if found and replace:
        p.b = s
#@+node:ekr.20130425050120.12671: *3* Replace in directory
'''Replace a search pattern with a replacement pattern in a directory.'''
# Adapted from 2to3's crlf fixer.

g.cls()
write = False
path = g.os_path_finalize_join(g.app.loadDir,'..','extensions','docutils_modernize_bad')
find = 'import six'
change = 'from docutils.utils.u import u,text_type'
import os

def fix(filename):
    if g.os_path_isdir(filename):
        # print filename, "Directory!"
        return
    s = open(filename,"rb").read()
    if '\0' in s:
        print('binary: %s' % filename)
        return
    new_s = s.replace(find,change)
    if new_s != s:
        print('changed: %s' % g.shortFileName(filename,n=2))
        if write:
            f = open(filename,"wb")
            f.write(new_s)
            f.close()

for root, dirs, files in os.walk(path):
    for fn in files:
        if fn.endswith('.py'):
            fn = g.os_path_join(root,fn)
            fix(fn)
print('done')
#@+node:ekr.20150416061628.1: ** Text processing
#@+node:ekr.20150416171133.1: *3* @ignore text-processing buttons
#@+node:ekr.20041228135008: *4* @button display c.p.b in the browser
@language python
@tabwidth -4

import webbrowser

if 0:
    << alternate code doesn't work well for me >>

def showHtml(html):
    '''Display html in a web browser'''
    fileName = g.os_path_join(g.app.loadDir,'..','test','leoTemp.html')
    f = file(fileName,'w')
    f.write(html)
    f.close()
    webbrowser.open(fileName)

# Display the selected node in the browser.
showHtml('<pre>%s</pre>' % c.p.b)

# To do: use a stylesheet to colorize the code.
#@+node:ekr.20041228140714: *5* << alternate code doesn't work well for me >>
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/347810
import BaseHTTPServer

def showHtml(html):
    """Display html in the default web browser without creating a temp file.

    Instantiates a trivial http server and calls webbrowser.open with a URL
    to retrieve html from that server.
    """

    class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
        def do_GET(self):
            g.trace(html)
            bufferSize = 1024*1024
            for i in xrange(0, len(html), bufferSize):
                self.wfile.write(html[i:i+bufferSize])

    server = BaseHTTPServer.HTTPServer(('127.0.0.1',0), RequestHandler)
    webbrowser.open('http://127.0.0.1:%s' % server.server_port)
    server.handle_request()
#@+node:ekr.20060821105606: *4* @button LeoToHtml
# LeoToHTML by Dan Rahmel

<< docstring >>

flagIgnoreFiles = True
flagJustHeadlines = True
filePath = "c:\\"

myFileName = c.frame.shortFileName()    # Get current outline filename
myFileName = myFileName[:-4]            # Remove .leo suffix
# Open file for output
f=open(filePath + myFileName + ".htm", 'w')

# Write HTML header information
f.write("<HTML>")
f.write("<BODY>")

for p in c.all_positions():
    myLevel = str(p.level() + 1)
    myHeadline = p.h
    # Check for node with LeoToHTML and ignore it
    if not myHeadline.upper() == "LEOTOHTML":
        if myHeadline[:5] != "@file" and not flagIgnoreFiles:
            # Write headline at current style level. indent level: 3
            f.write("<H" + myLevel + ">" + myHeadline + "</H" + myLevel + ">")
            # If including body text, convert it to HTML usable format
            if not flagJustHeadlines:
                myBody = p.b.encode( "utf-8" )
                f.write("<P>" + myBody)

# Write closing HTML info
f.write("</BODY>")  
f.write("</HTML>")

# Close file
f.close()
g.es(" Leo -> HTML completed.",color="turquoise4")
#@+node:ekr.20060821105606.1: *5* << docstring >>
'''
This script takes an outline stored in LEO and outputs it to HTML code.
The HTML can be stuck into a web page, loaded into the Microsoft Word
outline view for printing, or a million other uses.

--- Instructions ---
In LEO, open your outline, insert a node, and set the headline to LEOTOHTML.
Paste this code into the text body. To generate a .htm file of the outline,
select the Execute Script option under the Edit menu while the node is selected.

The switches below allow you to customize some of the settings. Set the flag
properties to True or False.
'''
#@+node:ekr.20060822112840: *4* @button LeoToRtf
# LeoToRTF by Dan Rahmel

<< docstring >>

# The switches below allow you to customize some of the settings.
flagIgnoreFiles = True
flagJustHeadlines = False
filePath = "c:\\"

myFileName = c.frame.shortFileName() # Get current outline filename
myFileName = myFileName[:-4] # Remove .leo suffix

g.es(" Leo -> RTF started...",color="turquoise4")

# Open file for output
f=open(filePath + myFileName + ".rtf", 'w')

# Write RTF header information
f.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\
fcharset0 Arial;}}")
f.write("{\\*\\generator LEOtoRTF;}\\viewkind4\\uc1\\pard\\f0\\fs20")

for p in c.all_positions():
    myLevel = str(p.level() + 1)
    myHeadline = p.h
    # Check for node with LeoToHTML and ignore it
    if not myHeadline.upper() == "LEOTORTF":
        if not (myHeadline[:5] == "@file" and flagIgnoreFiles):
            # Write headline with correct # of tabs for indentation
            myOutput = ("\\tab"*int(myLevel)) + " " + myHeadline +"\\par"
            myOutput = myOutput.encode( "utf-8" )
            f.write(myOutput)
            # If including outline body text, convert it to RTF usable format
            if not flagJustHeadlines:
                myBody = p.b.encode( "utf-8" ) 
                f.write("\\tab"*int(myLevel) + " " + myBody + "\\par")

# Write RTF close
f.write("}")  

# Close file
f.close()
g.es(" Leo -> RTF completed.",color="turquoise4")
#@+node:ekr.20060822112840.1: *5* << docstring >>
'''
This script takes an outline stored in LEO and outputs it to an RTF.
The tabbed RTF file can be loaded into Microsoft Word and formatted as a
proper outline with the Bullets and Numbering formatting options

--- Instructions ---
In LEO, open your outline, insert a node, and set the headline to LEOTORTF.
Paste this code into the text body. To generate a .rtf file of the outline,
select the Execute Script option under the Edit menu while the node is
selected.
'''
#@+node:ekr.20041229163210: *4* @button Send colorized text to web browser
'''A script to send the colorized text of a script to the default web browser.

Based on a dynascript by 'e'.'''

@language python
@tabwidth -4

<< imports >>

hopts = {
  'stripcomments':  False,
  'stripsentinals': True,
  'stripnodesents': False, # False: leave node sentinels.
  'stripdirectives':False,
  'noNUMBER':       False,
  'noOP':           False,
  'noNAME':         True,  
  'timestring':     '' # time.strftime('%m/%d/%Y %H:%M.%S'),
}

<< init globals >>

filename = g.os_path_join(g.app.loadDir,'..','test','leoTemp.html')

@others

htmlize(c,p)
#@+node:ekr.20041229164609: *5* << imports >>
import cgi
import cStringIO
import keyword
import os
import re
import sys
import time
import token
import tokenize
import webbrowser
#@+node:ekr.20041229163210.2: *5* << init globals >>
_KEYWORD = token.NT_OFFSET + 1
_TEXT    = token.NT_OFFSET + 2

_colors = {
    token.NUMBER:     '#483D8B', #black/darkslateblue
    token.OP:         '#000080', #black/navy
    token.STRING:     '#00AA00', #green 00cc66
    tokenize.COMMENT: '#DD0000', #red cc0033
    token.NAME:       '#4B0082', #black/indigo
    token.ERRORTOKEN: '#FF8080', #redred bare null does it
    _KEYWORD:         '#0066ff', #blue
    _TEXT:            '#000000', #black /is text fg color too
    '_LeoDir':        '#228B22', #directive, forest comment
    '_LeoSen':        '#BC8F8F', #sentinal, tan fade comment
    'bg':             '#FFFAFA', #snow
}

if hopts['noNUMBER']: del _colors[token.NUMBER]
if hopts['noOP']:     del _colors[token.OP]
if hopts['noNAME']:   del _colors[token.NAME]
#@+node:ekr.20041229170824: *5* stripSentinels
def stripSentinels(s):

    '''Strip sentinal lines from s.'''

    lines = s.splitlines()
    result = [] ; verbatim = False
    tag1 = '#@+node:'
    tag2 = '#@-node:'
    n = len(tag1)

    for line in lines:
        s = line.strip()
        if verbatim:
            result.append(line)
            verbatim = False
        elif s.startswith('#@verbatim'):
            verbatim = True
        elif s.startswith('#@@'):
            if not hopts['stripdirectives']:
                result.append(line)
        elif s.startswith(tag1):
            if not hopts['stripnodesents']:
                i = line.find(tag1)
                result.append(line[:i] + '#@+' + line[i+n:].strip())
        elif s.startswith(tag2):
            if not hopts['stripnodesents']:
                i = line.find(tag2)
                result.append(line[:i] + '#@-' + line[i+n:].strip())
        elif not s.startswith('#@'):
            result.append(line)

    return '\n'.join(result)
#@+node:ekr.20041229165956: *5* sanitize
def sanitize(s):

    """Leo's sanitize_filename is too aggressive and too lax."""

    if not s: return

    res = re.compile(
        r"""[|\\ /!@=\#\$%,\x5E&\x3F:;.\x22\x27<>`~\*\+\t\n\f\r\b\a]""",
        re.IGNORECASE | re.VERBOSE)

    # should test for unicode before str()
    return res.sub('_', str(s.strip())).replace('__','_')[:128]
#@+node:ekr.20041229163210.3: *5* class Parser
class Parser(object):
    """ prep the source for any language
        parse and Send colored python source.
    """
    @others
#@+node:ekr.20041229163210.4: *6* __init__
def __init__(self,lang,raw,title):
    """ Store the source text."""

    self.title = title
    self.raw = raw.strip().expandtabs(4)

    if lang == 'python':
        cmtdelim = '#'
    else:
        d = c.scanAllDirectives(p) 
        cmtdelim = d.get('delims',['#'])
        cmtdelim = cmtdelim[0] or cmtdelim[1]

    self.fnd = re.compile(r"%s@\s*@+."%(cmtdelim,) )

    if hopts['stripsentinals']: 
        # have to add option to strip all comments as well
        self.raw = stripSentinels(self.raw)
#@+node:ekr.20041229163210.5: *6* format
def format(self,formatter,form):
    """ Parse and send the colored source."""

    # store line offsets in self.lines
    self.lines = [0,0]
    pos = 0
    while 1:
        pos = self.raw.find('\n',pos) + 1
        if not pos: break
        self.lines.append(pos)
    self.lines.append(len(self.raw))
    self.pos = 0
    text = cStringIO.StringIO(self.raw)
    sys.stdout.write('<html><head><title>')
    sys.stdout.write('%s </title>\n'%(sanitize(self.title), ))

    #here would be a good spot for @noindent directive but skip a line
    s = """<STYLE TYPE="text/css"><!--
pre, H1 {color:%s; FONT-SIZE: 80%%; FONT-WEIGHT: bold; }
Text {background:%s;}
--></STYLE>
<SCRIPT LANGUAGE="JavaScript">
<!-- //
//-->
</SCRIPT>""" % (_colors[_TEXT],_colors['bg'])

    sys.stdout.write(s)
    sys.stdout.write('</head><body text="%s" bgColor="%s">' % (_colors[_TEXT],_colors['bg']))
    sys.stdout.write('<H3># %s</H3>\n'%self.title)
    sys.stdout.write('<pre>')  # style
    sys.stdout.write('<font face="Lucida,Courier New">')
    # parse the source and write it
    try:
        tokenize.tokenize(text.readline,self)
    except tokenize.TokenError, ex:
        msg = ex[0]
        line = ex[1][0]
        print "<h3>ERROR: %s</h3>%s" % (msg, self.raw[self.lines[line]:])
    sys.stdout.write('</font></pre>')
    sys.stdout.write('</body"></html>')
#@+node:ekr.20041229163210.6: *6* __call__
def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line):

    """ Token handler."""

    if 0: print "type", toktype, token.tok_name[toktype], "text",\
            toktext, "start", srow,scol, "end", erow,ecol, "<br>"

    # calculate new positions
    oldpos = self.pos
    newpos = self.lines[srow] + scol
    self.pos = newpos + len(toktext)

    # handle newlines
    if toktype in [token.NEWLINE, tokenize.NL]:
        print
        return

    style = ''
    if toktype == tokenize.COMMENT:
        if toktext.lstrip().startswith('#@'):
            if self.fnd.findall(toktext):
                toktype = '_LeoDir'
            else:
                toktype = '_LeoSen'

    # Send the original whitespace.
    if newpos > oldpos:
        sys.stdout.write(self.raw[oldpos:newpos])

    # Skip indenting tokens.
    if toktype in [token.INDENT,token.DEDENT]:
        self.pos = newpos
        return

    # Map token type to a color group
    if token.LPAR <= toktype and toktype <= token.OP:
        toktype = token.OP
    elif toktype == token.NAME and keyword.iskeyword(toktext):
        toktype = _KEYWORD
    if toktype == token.ERRORTOKEN:
        style = ' style="border: solid 1.5pt #FF0000;"'

    dofont = True
    try:
        color = _colors[toktype]
    except Exception:
        dofont = False

    if dofont: sys.stdout.write('<font color="%s"%s>' % (color, style))
    sys.stdout.write(cgi.escape(toktext))
    if dofont: sys.stdout.write('</font>')
#@+node:ekr.20041229164609.2: *5* htmlize
def htmlize(c,p):
    lang = g.scanForAtLanguage(c,p)
    lang = str(lang).lower()
    source = g.getScript(c,p)
    timestring = hopts['timestring']
    # title = "%s Leo %s script %s" % (p.h[:75],lang,timestring)
    title = "%s %s" % (p.h[:75],timestring)
    try:
        if not source: raise ValueError
        g.es('output', lang, p.h)
        theParser = Parser(lang,source,title)
        sys.stdout = open(filename,'wb') 
        if lang == 'python':
            theParser.format(None,None)
        else:
            << colorize with silvercity >>
        sys.stdout.close()
        sys.stdout = sys.__stdout__
        webbrowser.open(filename, new= 1)
    except ValueError:
        g.es('no @path set, unsupported lang or empty script',color='tomato')
        g.es(lang, p.h)
    except Exception:
        g.es('htmlize malfunction?', color='tomato')
        g.es_exception(full= True)
#@+node:ekr.20041229163210.7: *6* << colorize with silvercity >>
if lang in [ # Leo may not have all of these yet
    'csharp', 'c', 'c++', 'cpp', # (C and C++)
    'css', # (Cascading Style Sheets)
    'htm', 'html', # HTML/PHP w/ JavaScript, VBScript, Python
    'plain', #null (No styling)
    'perlpod', 'perl', # (Perl)
    #'python', # (Python)
    'ruby', # (Ruby)
    'smart_python', # (Python with styled strings)
    'sql', # (SQL)
    'xml', # (XML)
    'xslt', # (XSLT)
    'yaml', # (YAML)
    # basic & java? missing. might send java as c?
    'elisp', 'php', 'java', 'rapidq', 'actionscript', 'css',
]:
    if lang in ('htm','html','php','java','rapidq','actionscript', 'css'):
        lang = 'html'
    elif lang in ['c','c++','cpp']: lang = 'cpp'
    elif lang in ['perlpod','perl']: lang = 'perl'
    elif lang in ['elisp',]: lang = 'perl'
    if lang in ('plain',None): lang = 'null'

    g.es('writing tmpname', tmpfile )
    fo = file(tmpfile, 'w')
    fo.writelines(pars.raw + "\n")
    fo.close()

    cmd = g.os_path_join(pypath, 'Scripts', 'source2html.py')

    # Send the output to stdout
    #" --view %N  %N.html"
    # --css=file copy silver_city.css where the filename will be
    # source2html.py --list-generators
    params = ' --generator=%s --title=%s --css=silver_city.css %s'%(
       lang, sanitize_(title), tmpfile,)  

    if not g.os_path_exists(cmd):
        g.es('cant find source2html install silvercity')
        print 'cant find source2html from silvercity'
    else:
        g.es('running silvercity \n', py + cmd + params )
        out, err = runcmd(py + cmd + params )
        for x in (out + err).splitlines():
            print x
else:
    print '<i>not a known htmlize supported language</i>'
    #might have to do a sequential dump of nodes, or @rst?
    #is title and first headline set the same for all options?
    print '<Pre>'    
    print pars.raw
    print '</Pre>'
#@+node:ekr.20111004090723.15496: *3* Create pdf file from LaTeX file
<< docstring >>

@language python
@tabwidth -4

import os
import subprocess

@others

log = g.es_print

exe = r'C:\apps\MiKTeX 2.9\miktex\bin\pdflatex.exe'
    # The full path to the executable.

d = get_options(exe)

makePDF(d,open_out=True,trace=True)
#@+node:ekr.20111004185540.15535: *4* << docstring >>
@language rest

'''
A script for creating a pdf from a latex file.

*Important: you must run Leo from a console when using this script, at least
until the script can drive pdflatex in some kind of unattended mode. As it is
now, pdflatex is likely to hang, waiting for console input. The typical (only?)
response is to hit carriage returns until pdflatex finishes.

There are two modes of operation, static and dynamic, determined by the contents
of **d**, the **option dictionary**, returned by get_options.

**Important**: change get_options as described below to meet your needs.

If d.get('in_fn') exists, the script operates in static mode. Otherwise, the
script operates in dynamic mode.

Static mode
===========

d.get('in_fn') must be the full path to the input file.

If d.get('out_dir') exists, it must be the full path to the output directory.
The out_dir setting is optional. If omitted, output goes to the directory
containing the input file.

Dynamic mode
============

Rather than use d.get('in_fn'), the script finds the nearest @<file> node in
c.p.self_and_parents(). This node, *whatever it is*, is assumed to contain LaTeX
text.

**Important**: if in_fn is a relative path, it is converted to an absolute path
by taking account of any ancestor @path directive. If the resulting path is
still relative, it is taken to be a path relative to the directory containing
the .leo file. Here is the actual code::
    
    c_dir = g.os_path_dirname(c.fileName())
    fn = g.os_path_join(c_dir,c.getNodePath(p),fn)
    
If d.get('out_dir') exists, it may be either an absolute or relative path. If it
is a relative path, it is taken to be a path relative to the path given by the
@<file> node. That is::
    
    out_dir = g.os_path_join(g.os_path_dirname(fn),out_dir)

Other settings
==============

- exe:  The full path to the pdf creation program.
        'pdflatex' is part of MikTex.

- open_out: True: open the output file automatically after it is created.

- trace: True: output log messages using g.es_print.

The exe setting is a global variable.  The open_out and trace settings
are arguments to the makePDF function.

To do
=====

- Add an argument to the command that would have pdflatex run without user input.

- determine if the node held a rst file and first turn this into Latex.

- open the log file or read it into a node if there is a compiling error.

- It might be useful to have some settings in the file node that let the script
  automatically basic latex packages, items and definitions for a particular
  type of document (Title page, TOC, set date, use the beamer package to create
  slides, etc.)

- It would be really cool to be able to render TEX to PDF in real time like
  viewrendered handles HTML and ReST.
  
Acknowledgements
================

The original script by M.D.Boldin.  Rewritten by EKR.
'''

#@+node:ekr.20111004185540.15537: *4* get_options
def get_options(exe):
    
    '''Return the user options in a dict.'''
    
    if 0: # Static options...
        return {
            'exe': exe,
            'in_fn':    r'C:\Users\edreamleo\Latex\sample.tex',
                # The full path to the input file.
            'out_dir':  r'C:\Users\edreamleo\Latex\out',
                # The full path to the output directory.
        }
    else : # Dynamic options...
        '''The nearest @<file> gives the input file.
        
        If all paths are relative, the result is relative
        to the directory containing this .leo file.
        '''
    
        return {
            'exe': exe,
            'out_dir': 'out',
                # The output directory,
                # relative to the directory of the input file.
                # None is valid.
        }
#@+node:ekr.20111004185540.15540: *4* log
def log (s):
    
    '''Write a message to the log.'''
    
    g.es_print(s,color='red')
#@+node:ekr.20111004090723.15502: *4* makePDF
def makePDF (d,open_out,trace):
    
    command,out_fn = setup(d)
    if not command: return
    
    # Save the outline to save the file.
    c.save()

    if trace:
        log('\nMakePDF: command...')
        for arg in command:
            log('  %s' % arg)
        log('')

    # Execute the command and wait for it to finish.
    subprocess.Popen(command).wait()

    if open_out:
        if trace: log('Opening: %s' % out_fn)
        os.startfile(out_fn)
    
    if trace: log('Done')
#@+node:ekr.20111004182631.15511: *4* setup & helpers
def setup (d):
    
    '''Return the command to execute and the name of the output file.'''

    exe = d.get('exe')
    in_fn,out_dir = compute_file_names(d)

    if in_fn and out_dir:
        command = [
            exe,
            '-output-directory=%s' % (out_dir),
            in_fn
        ]
        out_fn = g.os_path_join(
            out_dir,g.shortFileName(in_fn[:-4]))+'.pdf'
        return command,out_fn
    else:
        return None,None
#@+node:ekr.20111004185540.15538: *5* compute_file_names
def compute_file_names (d):
    
    '''Return the absolute paths to the input file and the output directory.'''
    
    fn = d.get('in_fn')
    if fn:
        # Static, absolute paths.
        out_dir = d.get('out_dir') or g.os_path_dirname(fn)
        if g.os_path_exists(fn) and g.os_path_exists(out_dir):
            return fn,out_dir
        elif not g.os_path_exists(fn):
            log('File not found: %s' % fn)
            return None,None
        else:
            log('Directory not found: %s' % out_dir)
            return None,None
    else:
        # Dynamic file name from the nearest @<file> node.
        for p in c.p.self_and_parents():
            if p.isAnyAtFileNode():
                fn = p.anyAtFileNodeName()
                break
        else:
            log('no <@file> node found at: "%s"' % c.p.h)
            return None,None
        
        # Compute the absolute path to the filename,
        # taking into account any relevant @path directives.
        c_dir = g.os_path_dirname(c.fileName())
        fn = g.os_path_join(c_dir,c.getNodePath(p),fn)
        out_dir = d.get('out_dir') or ''
        out_dir = g.os_path_join(g.os_path_dirname(fn),out_dir)
        
        if g.os_path_exists(fn) and g.os_path_exists(out_dir):
            return fn,out_dir
        else:
            log('not found: %s' % fn)
            return None,None
#@+node:ekr.20050108110751.1: *3* Function: convert font tags to span tags
@ 
From Bill P.
Here is a function to convert the font tags to equivalent span tags. Note use
of font-size:x-large to show how styling can be used for emphasis.

Uses a regular expression to insert style CSS classes before </STYLE> closing
tag ...so <STYLE></STYLE> is expected to exist in input html.
@c

def font2span(colorizedusingFONT):
    aa=colorizedusingFONT
    styleClasses="""    .token_STRING    {color:#00AA00;} 
    .token_NUMBER    {color:#483D8B;}
    .token_OP        {color:#000080;} 
    .tokenize_COMMENT{color:#DD0000;} 
    .token_NAME      {color:#4B0082;} 
    .token_ERRORTOKEN{color:#FF8080;} 
    .KEYWORD        {color:#0066ff;} 
    .TEXT           {color:#000000;} 
    .LeoDir       {color:#228B22;} 
    .LeoSen       {color:#BC8F8F;font-size:x-large;} 
    .bg            {color:#FFFAFA;} 
    """
    myRE=re.compile(r"</STYLE>")              ;aa=myRE.sub(styleClasses+"\n</STYLE>",aa)
    myRE=re.compile(r'<font color="#00AA00">');aa=myRE.sub('<span class="token_STRING">',aa)
    myRE=re.compile(r'<font color="#483D8B">');aa=myRE.sub('<span class="token_NUMBER">',aa)
    myRE=re.compile(r'<font color="#000080">');aa=myRE.sub('<span class="token_OP">',aa)
    myRE=re.compile(r'<font color="#DD0000">');aa=myRE.sub('<span class="tokenize_COMMENT">',aa)
    myRE=re.compile(r'<font color="#4B0082">');aa=myRE.sub('<span class="token_NAME">',aa)
    myRE=re.compile(r'<font color="#FF8080">');aa=myRE.sub('<span class="token_ERRORTOKEN">',aa)
    myRE=re.compile(r'<font color="#0066ff">');aa=myRE.sub('<span class="KEYWORD">',aa)
    myRE=re.compile(r'<font color="#000000">');aa=myRE.sub('<span class="TEXT">',aa)
    myRE=re.compile(r'<font color="#228B22">');aa=myRE.sub('<span class="LeoDir">',aa)
    myRE=re.compile(r'<font color="#BC8F8F">');aa=myRE.sub('<span class="LeoSen">',aa)
    myRE=re.compile(r'<font color="#FFFAFA">');aa=myRE.sub('<span class="bg">',aa)
    myRE=re.compile(r'</font>')               ;aa=myRE.sub('</span>',aa)

    basefontSize="12pt"
    basefontFamily="Lucida,Courier New"
    myRE=re.compile(r'<font face="Lucida,Courier New">');
    aa=myRE.sub('<span style="font:'+basefontSize+' '+basefontFamily+';">',aa)

    return aa
#@+node:ekr.20050108110751.2: *4* NewHeadline
@ To adjust this script internally to use span tags and CSS style classes make
the following changes to  three nodes as follows:
@c

# I. in "init globals" node change the _colors dictionary to use lists 
# (with 3 values representing color, CSS class, extra styling) as follows:

_colors = {
    token.NUMBER:     ['#483D8B','token_NUMBER',' '], #black/darkslateblue
    token.OP:         ['#000080','token_OP',' '], #black/navy
    token.STRING:     ['#00AA00','token_STRING',' '], #green 00cc66
    tokenize.COMMENT: ['#DD0000','tokenize_COMMENT',' '], #red cc0033
    token.NAME:       ['#4B0082','token_NAME',' '], #black/indigo
    token.ERRORTOKEN: ['#FF8080','token_ERRORTOKEN',' '], #redred bare null does it
    _KEYWORD:         ['#0066ff','KEYWORD',' '], #blue
    _TEXT:            ['#000000','TEXT',' '], #black /is text fg color too
    '_LeoDir':        ['#228B22','LeoDir',' '], #directive, forest comment
    '_LeoSen':        ['#BC8F8F','LeoSen','font-size:x-large;'], #sentinal, tan fade comment
    'bg':             ['#FFFAFA','bg',' '], #snow
}
#@+node:ekr.20150416060855.1: ** Translation
#@+node:ekr.20071104221525.92: *3* @@button elisp test
@first # -*- coding: utf-8 -*-

import leoTest
import leoImport

@others

u = leoTest.testUtils(c) 
input_node  = u.findNodeAnywhere('new-elisp2py-input-')
output_node = u.findNodeAnywhere('new-elisp2py-output-')
assert input_node and output_node

for p in input_node.children():

    if 0: # No need for child node yet.
        root = output_node.insertAsLastChild()
        root.initHeadString(p.h)

    s = p.b
    scanner = elispScanner()
    aList = scanner.parse(s)
    if 1:
        print 'parse tree...'
        print scanner.dumpList(aList)
        print 'end parse tree'
        print
    result = scanner.gen(aList,indent=0,init=True)
    print scanner.dumpList(result)
#@+node:ekr.20071104221525.93: *4* class elispScanner
class elispScanner:

    @others
#@+node:ekr.20071104221525.94: *5*  __init__
def __init__ (self):

    # Debugging.
    self.debug = True
    self.trace = False

    # Semantic info.
    self.def_keywords = ('defun','defvar',)
    self.indent_keywords = ('if','prog','prog1','progn','set',)
    self.expr_keywords = ('and','not','or',)

    self.indent = 0 # Indentation of production output.

    # Dispatch dictionary.
    self.dispatchDict = {}

#@+node:ekr.20071104221525.95: *5* class token
class token:

    '''Representing one elisp syntactic entity,
    with a list of preceding comments.'''

    def __init__ (self,comments,kind,val):
        self.comments = comments[:]
        self.kind = kind
        self.val = val
            # For blocks, a list of tokens.
            # For all other tokens, the spelling of the token.

    def __repr__ (self):
        return '<token kind: %s, val: %s>' % (self.kind,self.val)

    def __str__ (self):
        if self.kind == 'block:':
            return 'block: [snip]'
        elif self.kind == 'string:':
            return '%s%s' % (self.kind,self.val[:20])
        else:
            return '%s%s' % (self.kind,self.val)
#@+node:ekr.20071104221525.96: *5* choose
def choose(self,cond,a,b): # warning: evaluates all arguments

    if cond: return a
    else: return b
#@+node:ekr.20071104221525.97: *5* dumpList
def dumpList(self,aList):

    if type(aList) == type([]):
        result = self.dumpListHelper(aList,indent=0)
        return '\n'.join(result)
    else:
        return repr(aList)

def dumpListHelper(self,aList,indent):

    result = []
    leading = ' ' * (4 * indent)

    for z in aList:
        if z is None:
            result.append('%s%s' % (leading,'None'))
        elif z == []:
            result.append('%s%s' % (leading,'[]'))
        elif type(z) == type([]):
            result.append('%s%s' % (leading,'['))
            result.extend(self.dumpListHelper(z,indent+1))
            result.append('%s%s' % (leading,']'))
        elif isinstance(z,self.token):
            if z.kind=='block:':
                result.append('%s%s' % (leading,'block:'))
                result.extend(self.dumpListHelper(z.val,indent+1))
                # result.append('%s%s' % (leading,'block:]'))
            else:
                result.append('%s%s' % (leading,str(z)))
        else:
            result.append('%s%s' % (leading,str(z)))

    return result

listToString = dumpList
#@+node:ekr.20071104221525.98: *5* Parsing...
#@+node:ekr.20071104221525.99: *6* parse
def parse(self,s):

    # Generate the nodes, including directive and section references.
    return self.scanForest(s)
#@+node:ekr.20071104221525.100: *6* scan & helpers
def scan(self,s,i):

    '''Scan an elisp expression.'''

    start = i ; end = len(s) ; result = []
    comments = [] ; token = self.token
    # A hack. ignore initial @language lisp
    tag = '@language lisp'
    if i == 0 and s[i:i+len(tag)]==tag:
        i += len(tag)
    while i < end:
        progress = i
        ch = s[i]
        if ch == ';':
            j = self.skipComment(s,i)
            # Comments are not tokens, they are attached to tokens.
            comments.append(s[i:j]) 
            start = i = j
        elif ch == '"':
            j = self.skipString(s,i)
            result.append(token(comments,'string:',s[i:j]))
            comments = []
            start = i = j
        elif ch.isalnum() or ch == u'_':
            j = self.skipId(s,i)
            result.append(token(comments,'id:',s[i:j]))
            comments = []
            start = i = j
        elif ch =='(':
            i += 1
            j,aList = self.scan(s,i)
            result.append(token(comments,'block:',aList))
            start = i = j
        elif ch == ')':
            i += 1
            return i,result
        else:
            if ch == "'": ch = 'quote'
            if ch not in (' ','\t','\n','\r'):
                result.append(token(comments,'op:',ch))
            i += 1
        assert progress < i,'i: %d, ch: %s' % (i,repr(s[i]))

    if start < end:
        tail = s[start:end].strip()
        if tail:
            result.append(token(comments,'tail:',tail))
            comment = []
    if comments:
        result.append(token(comments,'trailing-comment:',''))

    return i,result
#@+node:ekr.20071104221525.101: *7* skipComment
def skipComment (self,s,i):

    '''Skip a comment.'''

    while i < len(s):
        if s[i] == '\n':
            break
        i += 1
    return i
#@+node:ekr.20071104221525.102: *7* skipId
def skipId (self,s,i):

    while i < len(s):
        ch = s[i]
        if ch.isalnum() or ch in ('_','-'):
            i += 1
        else:
            break
    return i
#@+node:ekr.20071104221525.103: *7* skipString
def skipString(self,s,i):

    """Skip a string literal."""

    assert(s[i] == '"')
    i += 1
    while i < len(s):
        ch = s[i]
        if ch == '\\' : i += 2
        elif ch == '"':
            i += 1 ; break
        else: i += 1

    return i
#@+node:ekr.20071104221525.104: *6* scanForest
def scanForest (self,s):

    i = 0 ; result = []

    while i < len(s):
        progress = i
        i,aList = self.scan(s,i)
        aList and result.extend(aList)
        assert i > progress

    return result


#@+node:ekr.20071104221525.105: *5* Code generators...
@ From Richard Deibenkorn:

1. Attempt what is not certain. Certainty may or may not come later. It may then
be a valuable delusion.

2. The pretty, initial position which falls short of completeness is not to be
valued--except as stimulus for further moves.

3. Do search.  But in order to find other than what is looked for.

4. Use and respond to the initial fresh qualities but consider them absolutely
expendable.
#@+node:ekr.20071104221525.106: *6* gen
def gen(self,tokens,indent,init=False):

    result = []

    if init: result.append('='*40)

    for token in tokens:
        aList = self.gen_token(token,indent)
        result.extend(aList)

    if init: result.append('-'*40)

    return result

#@+node:ekr.20071104221525.107: *6* gen_token
def gen_token(self,token,indent):

    result = []

    if self.debug:
        if token.kind == 'block:':
            aList = self.gen_block(token,indent)
            result.extend(aList)
        else:
            self.put_token(token,indent,result)
    else:
        if token.kind == 'block:':
            self.gen_block(token,indent)
        else:
            self.put_code_token(token)

    return result
#@+node:ekr.20071104221525.108: *6* gen_block & helper
def gen_block (self,token,indent):

    if not (token and token.val):
        return []

    blockList = token.val
    token2 = blockList[0]
    result = []

    if token2.kind.startswith('id'):
        aList = self.gen_block_id(token2.val,blockList,indent)
    else:
        if self.debug:
            self.put('block...',[],indent,result)
        aList = self.gen(token.val,indent+1)

    result.extend(aList)
    return result
#@+node:ekr.20071104221525.109: *7* gen_block_id
def gen_block_id (self,theId,tokens,indent):

    result = []

    # Eventually there will be a lookup of the dispatch dict here.
    if theId == 'let':
        aList = self.gen_let(tokens,indent)
    elif theId == 'if':
        aList = self.gen_if(tokens,indent)
    elif theId in self.def_keywords:
        aList = self.gen_def(theId,tokens,indent)
    elif theId in self.indent_keywords:
        self.put('%s...' % (theId),[],indent,result)
        aList = self.gen(tokens[1:],indent+1)
    elif theId in self.expr_keywords:
        aList = self.gen_expr(theId,tokens[1:],indent+1)
    else:
        aList = self.gen_call(theId,tokens[1:],indent)

    result.extend(aList)
    return result
#@+node:ekr.20071104221525.110: *6* gen_call & helper
def gen_call (self,funcId,tokens,indent):

    result = []

    if self.debug:
        self.put('call: %s' % (funcId),[],indent,result)
        for token in tokens:
            aList = self.gen_arg(token,indent+1)
            result.extend(aList)
    else:
        self.put_code_line('%s(' % (funcId))
        for token in tokens:
            self.gen_arg(token,indent+1)
        self.put_code(')')

    return result

#@+node:ekr.20071104221525.111: *7* gen_arg
def gen_arg(self,token,indent):

    result = []

    if self.debug:
        if token.kind == 'block:':
            self.put('arg block:...',[],indent,result)
            aList = self.gen_block(token,indent)
            result.extend(aList)
        else:
            self.put_token(token,indent,result)
    else:
        if token.kind == 'block:':
            aList = self.gen_block(token,indent)
            self.put_code(''.join(aList))
        else:
            self.put_code_token(token)

    return result
#@+node:ekr.20071104221525.112: *6* gen_def
def gen_def(self,theId,tokens,indent):

    result = []

    if not tokens or len(tokens) < 3:
        result.append('*** bad def tokens')
        return result

    defToken,idToken = tokens[0:2]
    if idToken.kind != 'id:':
        result.append('*** bad def id')
        return result

    if self.debug:
        self.put(theId,idToken.val,indent,result)
        aList = self.gen(tokens[2:],indent+1)
        result.extend(aList)
    else:
        self.put_code('def %s (' % idToken.val)
        self.gen_token(tokens[2],indent)
        self.put_code('): # end def\n')
        self.indent += 1
        self.gen(tokens[3:],indent+1)
        self.indent -= 1

    return result
#@+node:ekr.20071104221525.113: *6* gen_if & helpers
@ if condition then-form else-forms.

If the evaluated condition is non-nil, then-form is evaluated and the result
returned. Otherwise, the else-forms are evaluated in textual order, and the
value of the last one is returned. If condition has the value nil, and no
else-forms are given, if returns nil.
@c

def gen_if (self,tokens,indent):

    # tokens[0]: id:if
    # tokens[1] cond

    for i in xrange(len(tokens)):
        g.trace('tokens[%d]: %s' % (i,self.dumpList(tokens[i])))

    # g.trace(self.dumpList(tokens))

    result = []

    if self.debug:
        self.put('if...',[],indent,result)
        aList = self.gen(tokens[1:],indent+1)
        result.extend(aList)
    else:
        self.put_code('if ')
        self.gen(tokens[1:],indent+1)
        self.put_code(': # end if\n')

    return result
#@+node:ekr.20071104221525.114: *7* gen_then
def gen_then (self,token):

    pass
#@+node:ekr.20071104221525.115: *6* gen_expr
def gen_expr (self,theId,aList,indent):

    binops = ('and','or',)
    result = []

    self.put(theId,[],indent,result)
    aList = self.gen(aList,indent+1)

    result.extend(aList)
    return result
#@+node:ekr.20071104221525.116: *6* gen_let & helper
@
(let ((variable value)
      (variable value)
      ...)
  body...)
@c

def gen_let (self,tokens,indent):

    if not tokens: return []
    if len(tokens) != 3:
        g.trace('unusual let')
        result = self.gen(tokens,indent+1)
        return result

    if 0:
        for i in xrange(len(tokens)):
            g.trace('token',i,tokens[i])

    letToken,bindingToken,bodyToken = tokens
    result = []
    self.put('let...',[],indent,result)
    self.put('let-bindings...',[],indent+1,result)
    aList = self.gen_let_bindings(bindingToken,indent+2)
    result.extend(aList)
    self.put('let-block...',[],indent+1,result)
    aList = self.gen_block(bodyToken,indent+2)
    result.extend(aList)
    return result
#@+node:ekr.20071104221525.117: *7* gen_let_bindings
def gen_let_bindings (self,token,indent):

    result = []

    if token.kind != 'block:':
        g.trace('unexpected let')
        return result

    for z in token.val:
        if z.kind == 'block:': # one (id,val) pair
            if z.val and len(z.val) == 2:
                token1 = z.val[0]
                token2 = z.val[1]
                self.put('let-id',token1,indent,result)
                self.put('let-val...',[],indent,result)
                if token2.kind == 'block:':
                    aList = self.gen_block(token2,indent+1)
                    result.extend(aList)
                else:
                    #g.trace('no let list')
                    self.put_token(token2,indent+1,result)
            else:
                g.trace('unexpected let 2')

    return result
#@+node:ekr.20071104221525.118: *6* put...
#@+node:ekr.20071104221525.119: *7* put
def put (self,kind,val,indent,result):

    '''Append one or more lines of output to result.'''

    leading = '%2d: %s' % (indent,' ' * indent)

    if kind == 'string:':
        val = self.choose(len(val)>20,val[:20]+'..."',val)

    if val:
        s = '%s%s %s' % (leading,str(kind),str(val))
    else:
        s = '%s%s' % (leading,str(kind))

    result.append(s)
#@+node:ekr.20071104221525.120: *7* put_token
def put_token (self,token,indent,result):

    for z in token.comments:
        self.put('comment:',z,indent,result)

    self.put(token.kind,token.val,indent,result)
#@+node:ekr.20071104221525.121: *7* put_code & put_code_line
def put_code_line (self,s):

    s2 = '%s%s' % (' '*self.indent,s)
    print s2,

def put_code (self,s):

    print s,
#@+node:ekr.20071104221525.122: *7* put_code_token
def put_code_token (self,token):

    if token.kind == 'block:':
        self.put_code('<block>')
    else:
        self.put_code(token.val)

#@+node:ekr.20060824111500.108: *3* @button jEdit2Py
'''Convert jEdit language description file to an equivalent .py file.'''

@language python
@tabwidth -4
@pagewidth 80

<< imports >>
<< to do >>
<< version history >>
<< set files to convert >>
opt_print_summary = False # True: print summary in doEndElement.
opt_print_elements = False
@others
main()
#@+node:ekr.20060824111500.109: *4* << imports >>
import glob
import os
import string
import xml.sax
import xml.sax.saxutils
#@+node:ekr.20060824111500.110: *4* << to do >>
@nocolor
@

AT_LINE_START, AT_WHITESPACE_END and AT_WORD_START attributes can also be used
on the BEGIN and END elements. Setting these attributes to the same value on
both elements has the same effect as setting them on the SPAN element.
#@+node:ekr.20060824111500.111: *4* << version history >>
@nocolor
@

7/23/06 EKR: Ignore keywords containing whitespace.
7/30/06 EKR: Compute keywordChars properly.  This fixes erlang bug, e.g.
8/24/06 EKR: Lowercase all keywords if ignore_case is true.
8/25/06 EKR: Prefixed all rule names with fileName (w/o extension) so I can makes sense of imported rules.
8/26/06 EKR: No need for hash_char in pattern matchers, because they are called only if the hash_char matches.
9/4/06  EKR: Added support for ESCAPE attribute of RULES element.
             This is required so the new colorizer plugin can support the no_escape argument to pattern matchers.
#@+node:ekr.20060824111500.112: *4* << set files to convert >>
# theDir = r'c:\prog\tigris-cvs\leo\modes'
theDir = g.os_path_abspath(g.os_path_join(g.app.loadDir,'..','modes'))
fixed = ['antlr','applescript','apacheconf','bibtex','cil','pl1','shell','shellscript','ssharp','text']
errors = [] # 'cil',] # End keyword not matched by start.
errors = [r'%s\%s.xml' % (theDir,s) for s in errors]
# for s in errors: print(s)
if 1: # A a list of files.
    short_files = ['asciidoc']
    files = [rf"{theDir}{os.path.sep}{z}.xml" for z in short_files]
elif 1: # translate all files.
    files = glob.glob(g.os_path_abspath(g.os_path_join(theDir,'*.xml')))
elif 1: # error files
    files = [g.os_path_abspath(g.os_path_join(theDir,s)) for s in errors]
else: # translate two representative files.
    files = [g.os_path_abspath(g.os_path_join(theDir,s)) for s in ('python','php')]
# for s in files: print(s)
#@+node:ekr.20060824111500.113: *4* top-level
#@+node:ekr.20160319080102.1: *5* cleanSaxInputString
def cleanSaxInputString(s):
    '''Clean control characters from s.
    s may be a bytes or a (unicode) string.'''
    # Note: form-feed ('\f') is 12 decimal.
    badchars = [chr(ch) for ch in range(32)]
    badchars.remove('\t')
    badchars.remove('\r')
    badchars.remove('\n')
    flatten = ''.join(badchars)
    pad = ' ' * len(flatten)
    # pylint: disable=no-member
    # Class 'str' has no 'maketrans' member
    if g.isPython3:
        flatten = bytes(flatten, 'utf-8')
        pad = bytes(pad, 'utf-8')
        transtable = bytes.maketrans(flatten, pad)
    else:
        transtable = string.maketrans(flatten, pad)
    return s.translate(transtable)
# for i in range(32): print i,repr(chr(i))
#@+node:ekr.20060824111500.115: *5* parse_jEdit_file
def parse_jEdit_file(inputFileName,language):

    if not inputFileName:
        return None
    if not inputFileName.endswith('.xml'):
        inputFileName = inputFileName + '.xml'
    path = g.os_path_join(g.app.loadDir,'../','modes',inputFileName)
    path = g.os_path_normpath(path)
    if not g.os_path_exists(path):
        g.es_print('not found:', path)
        return
    try:
        f = open(path,'r')
    except IOError:
        g.trace('can not open %s'%path)
        return None
    try:
        try:
            mode = None
            parser = xml.sax.make_parser()
                # Do not include external general entities.
                # The actual feature name is
                # "http://xml.org/sax/features/external-general-entities"
            parser.setFeature(xml.sax.handler.feature_external_ges,0)
            handler = contentHandler(c,inputFileName,language)
            parser.setContentHandler(handler)
            parser.parse(f)
                # Works with Python 3.
                # Fails with Python 2 when the file contains non-ascii characters.
            mode = handler.getMode()
        except:
            g.es('unexpected exception parsing %s' % (inputFileName),color='red')
            g.es_exception()
    finally:
        f.close()
        return mode
#@+node:ekr.20060824111500.114: *5* convert
def convert (c,inputFileName, outputFileName):

    junk, fn = g.os_path_split(inputFileName)
    language, junk = g.os_path_splitext(fn)
    mode = parse_jEdit_file(inputFileName, language)
    if not mode:
        g.trace('FAIL: no mode', language, inputFileName)
        return
    with open(outputFileName,'w') as f:
        try:
            mode.write(f, language)
            g.es_print('wrote', outputFileName)
        except IOError:
            g.es_print('can not create', outputFileName)
#@+node:ekr.20060824111500.116: *5* munge
def munge(s):

    '''Munge a mode name so that it is a valid python id.'''

    valid = string.ascii_letters + string.digits + '_'
    return ''.join([g.choose(ch in valid,ch.lower(),'_') for ch in s])
#@+node:ekr.20160319081141.1: *5* main
def main():
    
    if not g.isPython3:
        g.es_print('Python 3 is now required for this script.')
        return
    for path1 in files:
        if path1 in errors:
            print(f"skipping {path1}")
            continue
        path2 = path1[:-3] + 'py'
        try:
            convert (c, path1, path2)
        except Exception:
            print('Exception creating', path2)
            g.es_exception()
    g.es_print('done')
#@+node:ekr.20060824111500.117: *4* class modeClass
class modeClass:

    '''A class representing one jEdit language-description mode.

    Use getters to access the attributes, properties and rules of this mode.'''

    @others
#@+node:ekr.20060824111500.118: *5*  mode.__init__
def __init__ (self,contentHandler,fileName):

    # g.trace('mode',fileName)

    self.contentHandler = contentHandler
    self.c = contentHandler.c
    self.fileName = g.shortFileName(fileName) # The file from which the mode was imported.
    modeName, junk = g.os_path_splitext(self.fileName)
    self.fileModeName = modeName
    self.modeName = munge(modeName).lower()
    self.outputFile = None # The open output file to which Python statements get written.
    self.tab_width = c.scanAllDirectives().get('tab_width')

    # Mode statistics...
    self.numberOfAttributes = 0
    self.numberOfElements = 0
    self.numberOfErrors = 0
    self.numberOfPropertyAttributes = 0
    self.numberOfRuleAttributes = 0

    # List of boolean attributes.
    self.boolAttrs = [
        'at_line_start','at_whitespace_end','at_word_start',
        'exclude_match','highlight_digits','ignore_case',
        'no_escape','no_line_break','no_word_break',]

    # List of elements that start a rule.
    self.ruleElements = [
        'eol_span','eol_span_regexp','import','keywords',
        'mark_following','mark_previous','seq','seq_regexp',
        'span','span_regexp','terminate',]

    if 0: # Not used at present.
        self.seqSpanElements = [
            'eol_span','eol_span_regexp','seq','seq_regexp',
            'span','span_regexp',]

    # Mode semantics.
    self.attributes = {}
    self.handlerCount = 0
    self.importedRules = [] # A bunch describing the imported ruleset.
    self.inProps = False
    self.inRules = False
    self.keywords = None
    self.modeProperties = []
    self.presentProperty = None # A bunch to be assigned to modeProperties or rulesetProperties.
    self.rule = None
    self.rulesets = []
    self.rules = [] # The rules of the present rules element.
    self.rulesetProperties = []
    self.rulesetAttributes = {} # The attributes of the present rules element.
#@+node:ekr.20060824111500.119: *5*  mode.__str__ & __repr__
def __str__ (self):

    return '<modeClass for %s>' % self.fileName

__repr__ = __str__
#@+node:ekr.20060824111500.120: *5*  Output...
# Similar to printing, but generates the output file.
#@+node:ekr.20060827162343: *6* fullDelegate
def fullDelegate (self,delegate):

    if delegate:
        delegate = delegate.lower()
        i = delegate.find('::')
        if i == -1:
            return '%s::%s' % (self.fileModeName.lower(),delegate)
        else:
            return delegate
    else:
        return ''
#@+node:ekr.20060824111500.121: *6* escapeString & quoteString
def escapeString (self,s):
    '''Return string s enclosed in double quotes.'''
    if not s: s = ''
    if isinstance(s, (list, tuple)):
        s = ''.join(s)
    s = g.toUnicode(s)
    # Order is important: escape backspaces first.
    return '"%s"' % s.replace('\\','\\\\').replace('"','\\"').replace('\t','\\t')

quoteString = escapeString
#@+node:ekr.20060824111500.122: *6* put, putTripleString
def put (self,s):

    self.outputFile.write(s)

def putTripleString(self,s):

    self.put(g.adjustTripleString(s,self.tab_width))
#@+node:ekr.20060824111500.123: *6* putAttributes
def putAttributes (self):

    dd = {}
    data = (
        ('default','null'),
        ('digit_re',''),
        ('escape',''),
        ('highlight_digits',True),
        ('ignore_case',True),
        ('no_word_sep',None), # could be false or ''.
    )

    for ruleset in self.rulesets:
        d = {}
        prefix = '%s_%s' % (self.modeName,ruleset.name)
        self.put('# Attributes dict for %s ruleset.\n' % (prefix))
        for key,default in data:
            val = ruleset.attributes.get(key,default)
            if default == True: val = g.choose(val,'true','false')
            elif default == None:
                if val and val.lower() == 'false': val = ''
                else: val = g.choose(val,val,'')
            # if val: g.trace(key,repr(val))
            d [key] = val
        self.putDict('%s_attributes_dict' % (prefix),d)
        dd [ prefix ] = '%s_attributes_dict' % (prefix)

    self.put('# Dictionary of attributes dictionaries for %s mode.\n' % (self.modeName))
    self.putDict('attributesDictDict',dd,escape=False)
#@+node:ekr.20060824111500.124: *6* putDict & putDictOfLists
def putDict (self,name,theDict,escape=True):

    esc = self.escapeString
    esc2 = g.choose(escape,self.escapeString,lambda a: a)
    keys = list(set((theDict.keys())))
    keys = sorted([g.toUnicode(z) for z in keys if z])
    s = ''.join([
        '\t%s: %s,\n' % (esc(key),esc2(theDict.get(key)))
            for key in keys])
    if s:
        s = '\n' + s
    self.put('%s = {%s}\n\n' % (name,s))

def putDictOfLists (self,name,theDict,strings=False):

    esc = self.escapeString
    keys = list(set(theDict.keys()))
    keys = sorted([g.toUnicode(z) for z in keys if z])
    theList = []
    for key in keys:
        if strings:
            # Not completely general, but it works for the import dict, and that's good enough.
            s = ''.join(['"%s",' % (item) for item in theDict.get(key)])
        else:
             s = ''.join(['%s,' % (item) for item in theDict.get(key)])
        theList.append('\t%s: [%s],\n' % (esc(key),s))
    s = ''.join(theList)
    if s: s = '\n' + s
    self.put('%s = {%s}\n\n' % (name,s))
#@+node:ekr.20060824111500.125: *6* putImportDict
def putImportDict (self):

    d = {}
    for ruleset in self.rulesets:
        prefix = '%s_%s' % (self.modeName,ruleset.name)
        for rule in ruleset.rules:
            if rule.name == 'import':
                delegate = rule.getStrAttrib('delegate').lower()
                if delegate:
                    i = delegate.find('::')
                    delegate_name = g.choose(i==-1,
                        '%s::%s' % (prefix,delegate), # Can indeed happen.
                        '%s' % (delegate))
                    theList = d.get(prefix,[])
                    if delegate_name not in theList:
                        theList.append(delegate_name)
                        d [prefix] = theList

    self.put('# Import dict for %s mode.\n' % (self.modeName))
    self.putDictOfLists('importDict',d,strings=True)
#@+node:ekr.20060824111500.126: *6* putKeywordsData
def putKeywordsData (self):

    dd = {}
    for ruleset in self.rulesets:
        prefix = '%s_%s' % (self.modeName,ruleset.name)
        prefix = g.toUnicode(prefix)
        ignore_case = ruleset.attributes.get('ignore_case','false')
        if not isinstance(ignore_case, bool):
            ignore_case = ignore_case.lower() == 'false'
        self.put('# Keywords dict for %s ruleset.\n' % (prefix))
        for rule in ruleset.rules:
            if rule.name == 'keywords':
                # d = rule.keywordsDict
                d = { g.toUnicode(key): g.toUnicode(val)
                    for key, val in rule.keywordsDict.items() if key.strip() }
                # g.trace()
                # g.printObj(d)
                for key in sorted(set(d.keys())):
                    if ' ' in key or '\t' in key:
                        del d [key]
                        g.es_print(f"Ignoring keyword containing whitespace: {key!r}")
                    elif ignore_case: # New in 4.4.1 final.
                        # Downcase all keys.
                        val = d.get(key)
                        key2 = key.lower()
                        if key2 != key:
                            if key in d: del d[key]
                        d[key2] = val
                break
        else:
            d = {}
        self.putDict('%s_keywords_dict' % (prefix),d)
        dd [ prefix ] = '%s_keywords_dict' % (prefix)
    self.put('# Dictionary of keywords dictionaries for %s mode.\n' % (self.modeName))
    self.putDict('keywordsDictDict',dd,escape=False)
#@+node:ekr.20060824111500.127: *6* putList
def putList (self,name,theList,escape=True,lineCount=0):

    def comma(i):
        return ',' # return g.choose(i==len(theList)-1,'',',')
    def nl(i):
        return g.choose(lineCount == 0 or ((i%lineCount)==lineCount-1),'\n',' ')
    def tab(i,n):
        # return g.choose(i == 0 or nl(i-1) != ' ','\t','')
        return g.choose(i > 0 and nl(i-1) != ' ','\t','')
    esc = g.choose(escape,self.escapeString,lambda a:a)

    result = []
    n = len(theList)
    for i in range(n):
        result.append('%s%s%s%s' % (tab(i,n),esc(theList[i]),comma(i),nl(i)))

    vals = ''.join(result)
    # if n > 1: vals = '\n' + vals
    self.put('%s = [%s]\n\n' % (name,vals))
#@+node:ekr.20060824111500.128: *6* putModeProperties
def putModeProperties (self,language):

    d = {}

    self.put('# Properties for %s mode.\n' % (language))

    for prop in self.modeProperties:
        d2 = prop.attributes
        name = d2.get('name')
        d [name] = d2.get('value')

    self.putDict('properties', d)
#@+node:ekr.20060824111500.129: *6* putRule & rule creators
def putRule (self, rule):

    '''Call the rule creator for the given rule.'''

    d = {
        'eol_span':         self.putEolSpan,
        'eol_span_regexp':  self.putEolSpanRegexp,
        'import':           self.putImport,
        'keywords':         self.putKeywords,
        'mark_following':   self.putMarkFollowing,
        'mark_previous':    self.putMarkPrevious,
        'seq':              self.putSeq,
        'seq_regexp':       self.putSeqRegexp,
        'span':             self.putSpan,
        'span_regexp':      self.putSpanRegexp,
        'terminate':        self.putTerminate,
    }

    # Call the rule creator.
    f = d.get(rule.name,self.putBadRule)
    val = f (rule)
    self.handlerCount += 1

    return val
#@+node:ekr.20060824111500.130: *7* putBadRule
def putBadRule (self,rule):

    self.put('\n\n# *****no output creator for %s*****' % rule.name)
#@+node:ekr.20060824111500.131: *7* putEolSpan
def putEolSpan (self,rule):

    quote = self.quoteString
    seq = rule.getSeq(rule.name)

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_eol_span(s, i, kind=%s, seq=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s,
        delegate=%s, exclude_match=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(seq),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        quote(self.fullDelegate(rule.getStrAttrib('delegate'))),
        rule.getBoolAttrib('exclude_match'),
    )

    self.putTripleString(s)
    return seq[0]
#@+node:ekr.20060824111500.132: *7* putEolSpanRegexp
def putEolSpanRegexp (self,rule):

    quote = self.quoteString
    seq = rule.getSeq(rule.name)
    hash_char = rule.getStrAttrib('hash_char') or seq[0]
    # g.trace('hash_char',hash_char)

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_eol_span_regexp(s, i, kind=%s, regexp=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s,
        delegate=%s, exclude_match=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(seq),
        # quote(rule.getStrAttrib('hash_char')),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        quote(self.fullDelegate(rule.getStrAttrib('delegate'))),
        rule.getBoolAttrib('exclude_match'),
    )

    self.putTripleString(s)
    return hash_char  # Bug fix: was seq[0]
#@+node:ekr.20060824111500.133: *7* putImport
# Do nothing here: putImportDict creates x.importDict.

def putImport (self,rule):

    # Decrement the count to indicate that this method did not generate a rule.
    self.handlerCount -= 1
    return ''
#@+node:ekr.20060824111500.134: *7* putKeywords
def putKeywords (self,rule):

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_keywords(s, i)''' % (
    self.fileModeName,
    self.handlerCount)

    self.putTripleString(s)
    return 'keywords'
#@+node:ekr.20060824111500.135: *7* putMarkFollowing
def putMarkFollowing (self,rule):

    quote = self.quoteString
    seq = rule.getSeq(rule.name)

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_mark_following(s, i, kind=%s, pattern=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s, exclude_match=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(seq),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        rule.getBoolAttrib('exclude_match'),
    )

    self.putTripleString(s)
    return seq[0]
#@+node:ekr.20060824111500.136: *7* putMarkPrevious
def putMarkPrevious (self,rule):

    quote = self.quoteString
    seq = rule.getSeq(rule.name)

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_mark_previous(s, i, kind=%s, pattern=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s, exclude_match=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(seq),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        rule.getBoolAttrib('exclude_match'),
    )

    self.putTripleString(s)
    return seq[0]
#@+node:ekr.20060824111500.137: *7* putSeq
def putSeq (self,rule):

    quote = self.quoteString
    seq = rule.getSeq(rule.name)

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_seq(s, i, kind=%s, seq=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s, delegate=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(seq),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        quote(self.fullDelegate(rule.getStrAttrib('delegate'))),
    )

    self.putTripleString(s)
    return seq[0]
#@+node:ekr.20060824111500.138: *7* putSeqRegexp
def putSeqRegexp (self,rule):

    quote = self.quoteString
    seq = rule.getSeq(rule.name)
    hash_char = rule.getStrAttrib('hash_char') or seq[0]
    # g.trace('hash_char',hash_char)

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_seq_regexp(s, i, kind=%s, regexp=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s, delegate=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(seq),
        # quote(rule.getStrAttrib('hash_char')),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        quote(self.fullDelegate(rule.getStrAttrib('delegate'))),
    )

    self.putTripleString(s)
    return hash_char # Bug fix: was seq[0]
#@+node:ekr.20060824111500.139: *7* putSpan
def putSpan (self,rule):

    quote = self.quoteString
    begin,end = rule.getSpan()

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_span(s, i, kind=%s, begin=%s, end=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s,
        delegate=%s,exclude_match=%s,
        no_escape=%s, no_line_break=%s, no_word_break=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(begin),quote(end),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        quote(self.fullDelegate(rule.getStrAttrib('delegate'))),
        rule.getBoolAttrib('exclude_match'),
        rule.getBoolAttrib('no_escape'),
        rule.getBoolAttrib('no_line_break'),
        rule.getBoolAttrib('no_word_break'),
    )

    self.putTripleString(s)
    return begin[0]
#@+node:ekr.20060824111500.140: *7* putSpanRegexp
def putSpanRegexp (self,rule):

    quote = self.quoteString
    begin,end = rule.getSpan()
    hash_char = rule.getStrAttrib('hash_char') or begin[0]

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_span_regexp(s, i, kind=%s, begin=%s, end=%s,
        at_line_start=%s, at_whitespace_end=%s, at_word_start=%s,
        delegate=%s,exclude_match=%s,
        no_escape=%s, no_line_break=%s, no_word_break=%s)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        quote(begin),quote(end),
        # quote(rule.getStrAttrib('hash_char')),
        rule.getBoolAttrib('at_line_start'),
        rule.getBoolAttrib('at_whitespace_end'),
        rule.getBoolAttrib('at_word_end'),
        quote(self.fullDelegate(rule.getStrAttrib('delegate'))),
        rule.getBoolAttrib('exclude_match'),
        rule.getBoolAttrib('no_escape'),
        rule.getBoolAttrib('no_line_break'),
        rule.getBoolAttrib('no_word_break'),
    )

    self.putTripleString(s)
    return hash_char # Bug fix: was begin[0]
#@+node:ekr.20060824111500.141: *7* putTerminate
def putTerminate (self,rule):

    quote = self.quoteString

    n = rule.getIntAttrib('at_char')
    if n == None: return

    s = '''\n\
def %s_rule%d(colorer, s, i):
    return colorer.match_terminate(s, i, kind=%s, at_char=%d)''' % (
        self.fileModeName,
        self.handlerCount,
        quote(rule.getStrAttrib('type').lower()),
        n)

    self.putTripleString(s)
#@+node:ekr.20060824111500.142: *6* putRules
def putRules (self):

    '''Create all rule matchers, a rules dict for each ruleset and x.rulesDictDict.'''

    d = {} ; d2Count = 0
    for ruleset in self.rulesets:
        d2 = {}
        prefix = '%s_%s'% (self.modeName,ruleset.name)
        self.put('# Rules for %s ruleset.\n' % (prefix))
        for rule in ruleset.rules:
            ch = self.putRule(rule)
            self.put('\n')
            if ch == 'keywords':
                for ch in self.keywordChars:
                    theList = d2.get(ch,[])
                    theList.append('%s_rule%d' % (self.fileModeName,self.handlerCount-1))
                    d2 [ch] = theList
            elif ch:
                theList = d2.get(ch,[])
                theList.append('%s_rule%d' % (self.fileModeName,self.handlerCount-1))
                d2 [ch] = theList
        # Create the rules dict for the ruleset.
        self.put('\n# Rules dict for %s ruleset.\n' % (prefix))
        d2Count += 1
        name = 'rulesDict%d' % d2Count
        self.putDictOfLists(name,d2)
        d [prefix] = name
    # Create rulesDictDict.
    self.put('# x.rulesDictDict for %s mode.\n' % (self.modeName))
    self.putDict('rulesDictDict', d, escape=False)
#@+node:ekr.20060824111500.143: *6* write
def write (self,theFile,language):

    # Compute all the letters that can occur in a keyword.
    self.keywordChars = [ch for ch in string.ascii_letters + string.digits + '@']
    for ruleset in self.rulesets:
        for rule in ruleset.rules:
            d = rule.keywordsDict
            for key in list(d.keys()):
                key = g.toUnicode(key)
                for ch in key:
                    if ch not in self.keywordChars and ch not in (' ','\t','\n'):
                        self.keywordChars.append(ch)
        
    # g.trace('keywordChars',repr(self.keywordChars))
    self.keywordChars = ''.join(list(set(self.keywordChars)))
    self.outputFile = theFile
    self.put('# Leo colorizer control file for %s mode.\n' % language)
    self.put('# This file is in the public domain.\n\n')
    self.putModeProperties(language)
    self.putAttributes()
    self.putKeywordsData()
    self.putRules()
    self.putImportDict()
#@+node:ekr.20060824111500.144: *5*  Printing...
#@+node:ekr.20060824111500.145: *6* printModeAttributes, printRulesetAttributes & printAttributesHelper
def printModeAttributes (self):

    self.printAttributesHelper('mode attributes',self.attributes)

def printRulesetAttributes (self,ruleset,tag=None):

    if not tag: tag = 'main ruleset'

    self.printAttributesHelper(tag,ruleset.attributes)

def printAttributesHelper (self,kind,attrs):

    print('%-15s' % (kind),'attrs:',attrs)
#@+node:ekr.20060824111500.146: *6* printProperty
def printProperty (self,theProperty):

    # A property is a bunch.
    d = theProperty.attributes
    if d:
        self.printAttributesHelper('property',d)
#@+node:ekr.20060824111500.147: *6* printRule
def printRule (self,rule):

    # A rule is a g.Bunch.
    if rule.name == 'keywords':
        print('%-15s' % ('rule:keywords')) ## ,
        d = rule.keywordsDict
        d2 = {}
        for key in d:
            val = d.get(key)
            d2 [val] = d2.get(val,0) + 1
        keys = list(d2.keys())
        keys.sort()
        for key in keys:
            print('%s: %d' % (key,d2.get(key))) ## ,
        print()
    else:
        d = rule.attributes
        d2 = rule.contents
        if d or d2:
            print('%-15s' % ('rule:'+rule.name)) ## ,
            if d:  print('attrs:',d) ## ,
            if d2: print('contents:',d2) ## ,
            print()
#@+node:ekr.20060824111500.148: *6* printRuleset
def printRuleset (self,ruleset,tag):

    self.printRulesetAttributes(ruleset,tag)

    for rule in self.rulesets[0].rules:
        self.printRule(rule)
#@+node:ekr.20060824111500.149: *6* printSummary
def printSummary (self,printStats=True):

    if printStats:
        print('-' * 10, 'mode statistics')
        print('elements',self.numberOfElements)
        print('errors',self.numberOfErrors)
        print('mode attributes',self.numberOfAttributes)
        print('property attributes',self.numberOfPropertyAttributes)
        print('rule attributes',self.numberOfRuleAttributes)

    self.printModeAttributes()

    for bunch in self.modeProperties:
        self.printProperty(bunch)

    self.printRuleset(self.rulesets[0],tag='main ruleset')
#@+node:ekr.20060824111500.150: *5* doAttribute
def doAttribute (self,name,val):

    name = str(name.lower())

    if name in self.boolAttrs:
        val = g.choose(val.lower()=='true',True,False)
    else:
        val = str(val) # Do NOT lower this value!

    if self.rule:
        d = self.rule.attributes
        d [name] = val
        self.numberOfRuleAttributes += 1
    elif self.presentProperty:
        d = self.presentProperty.get('attributes')
        d [name] = val
        self.numberOfPropertyAttributes += 1
    elif self.inRules:
        self.rulesetAttributes[name] = val
        self.numberOfAttributes += 1
    else:
        self.attributes[name] = val
        self.numberOfAttributes += 1
#@+node:ekr.20060824111500.151: *5* doContent
def doContent (self,elementName,content):

    if not content:
        return

    name = str(elementName.lower())

    if self.inRule('keywords'):
        # g.trace('in keywords',name,content)
        d = self.rule.keywordsDict
        d [ content ] = name

    elif self.rule:
        d = self.rule.contents
        s = d.get(name,'')
        d [name] = s + g.toUnicode(content)
        self.contents = d
#@+node:ekr.20060824111500.152: *5* endElement
def endElement (self,elementName):

    name = elementName.lower()

    if name == 'props':
        self.inProps = True
    if name == 'rules':
        self.inRules = False
        ruleset = rulesetClass(self.rulesetAttributes,self.keywords,self.rulesetProperties,self.rules)
        self.rulesets.append(ruleset)
        #g.trace('rules...\n',g.listToString(self.rules))
        #g.trace('ruleset attributes...\n',g.dictToString(self.rulesetAttributes))
    if name == 'property':
        bunch = self.presentProperty
        if bunch:
            if self.inRules:
                self.rulesetProperties.append(bunch)
            else:
                self.modeProperties.append(bunch)
        else:
            self.error('end %s not matched by start %s' % (name,name))
        self.presentProperty = None
    if name in self.ruleElements:
        if self.inRule(name):
            self.rules.append(self.rule)
            self.rule = None
        else:
            self.error('end %s not matched by start %s' % (name,name))
#@+node:ekr.20060824111500.153: *5* error
def error (self,message):

    self.numberOfErrors += 1

    self.contentHandler.error(message)
#@+node:ekr.20060824111500.154: *5* getters
def getAttributes (self):
    return self.attributes

def getAttributesForRuleset (self,ruleset):
    bunch = ruleset
    return bunch.attributes

def getFileName (self):
    return self.fileName

def getKeywords (self,n,ruleset):
    bunch = ruleset
    keywords = bunch.keywords
    if keywords:
        return keywords.get('keyword%d'%(n),[])
    return []

def getLanguage (self):
    path,name = g.os_path_split(self.fileName)
    language,ext = g.os_path_splitext(name)
    return language

def getPropertiesForMode (self):
    return self.props

def getPropertiesForRuleset (self,name=''):
    bunch = self.getRuleset(name)
    if bunch:
        return bunch.properties
    else:
        return []

def getRuleset(self,name=''):
    if not name:
        return self.rulesets[0] # Return the main ruleset.
    for ruleset in self.rulesets:
        if ruleset.name.lower()==name.lower():
            return ruleset
    else: return None

def getRulesets(self):
    return self.rulesets

def getRulesForRuleset (self,name=''):
    bunch = self.getRuleset(name)
    if bunch:
        return bunch.rules
    else:
        return []
#@+node:ekr.20060824111500.155: *5* inRule
def inRule (self,elementName):

    return self.rule and self.rule.name == elementName
#@+node:ekr.20060824111500.156: *5* startElement
def startElement (self,elementName):

    name = elementName.lower()

    if name == 'props':
        self.inProps = True
    if name == 'rules':
        self.inRules = True
        self.attributes=[]
        self.keywords=[]
        self.rulesetProperties=[]
        self.rules=[]
    if name == 'property':
        if self.inProps:
            self.presentProperty = g.bunch(name=name,attributes={})
        else:
            self.error('property not in props element')
    if name in self.ruleElements:
        if self.inRules:
            self.rule = ruleClass(name=name)
            if name == 'keywords':
                self.keywords = self.rule
        else:
            self.error('%s not in rules element' % name)
#@+node:ekr.20060824111500.157: *4* class rulesetClass
class rulesetClass:

    @others
#@+node:ekr.20060824111500.158: *5* ctor & __str__
def __init__ (self,attributes,keywords,properties,rules):

    self.name=munge(attributes.get('set','main'))
    self.attributes=attributes.copy() # A dict.
    self.properties=properties[:] # A list.
    self.keywords=keywords # A bunch.
    self.rules=rules[:] # A list.

    # g.trace('ruleset',self.name or 'main')

    self.defaultColor = self.attributes.get('default')

def __str__ (self):

    return '<ruleset %s>' % self.name

__repr__ = __str__
#@+node:ekr.20060824111500.159: *4* class ruleClass
class ruleClass:

    '''A class to represent one xml rule.'''

    @others
#@+node:ekr.20060824111500.160: *5* ctor & __str__
def __init__ (self,name):

    self.attributes = {}
    self.contents = {}
    self.keywordsDict = {}
    self.name = name

def __str__ (self):

    return '<rule %s\nattr: %s\ncontents: %s>' % (
        self.name,g.dictToString(self.attributes),g.dictToString(self.contents))

__repr__ = __str__
#@+node:ekr.20060824111500.161: *5* rule.getters
def getBoolAttrib(self,name):
    d = self.attributes
    val = d.get(name)
    return g.choose(val,'True','False')

def getIntAttrib(self,name):
    d = self.attributes
    val = d.get(name)
    if val is not None:
        try:
            val = int(val)
        except ValueError:
            g.trace('bad int argument: %s = %s' % (name,val))
            val = None
    return val

def getSpan (self):
    d = self.contents
    begin = d.get('begin','')
    end   = d.get('end','')
    return begin,end

def getStrAttrib(self,name):
    d = self.attributes
    val = d.get(name,'')
    return str(val)

def getSeq(self,kind):
    # g.trace(repr(self.contents))
    d = self.contents
    return d.get(kind,'')
#@+node:ekr.20060824111500.162: *4* class contentHandler (xml.sax.saxutils.XMLGenerator)
class contentHandler (xml.sax.saxutils.XMLGenerator):

    '''A sax content handler class that handles jEdit language-description files.

    Creates mode that can be retrieved using the getMode method.'''

    @others
#@+node:ekr.20060824111500.163: *5*  __init__ & helpers
def __init__ (self,c,inputFileName,language):

    self.c = c
    self.inputFileName = inputFileName
    self.language = language

    # Init the base class.
    xml.sax.saxutils.XMLGenerator.__init__(self)

    # Non-mode statistics.
    self.numberOfAttributes = 0
    self.numberOfElements = 0

    # Options...
    self.ignoreWs = True # True: don't print contents with only ws.
    self.newLineAfterStartElement = [
        'keywords','mode','props','property','rules','span','eol_span',
        # 'seq',
    ]

    # Printing options
    if opt_print_elements:
        self.printAllElements = True
        self.printCharacters = False or self.printAllElements
        self.printAttributes = False and not self.printAllElements
        self.printElements = [
            #'begin','end',
            #'eol_span',
            #'keyword1','keyword2','keyword3','keyword4',
            #'mark_previous',
            #'mode',
            #'props',
            #'property',
            #'rules',
            #'span',
            #'seq',
        ]

        if self.printAllElements:
            self.suppressContent = []
        else:
            self.suppressContent = ['keyword1','keyword2','keyword3','keyword4']
    else:
        self.printAllElements = False
        self.printCharacters = False
        self.printAttributes = False
        self.printElements = []

    # Semantics: most of these should be mode ivars.
    self.elementStack = []
    self.errors = 0
    self.mode = None # The present mode, or None if outside all modes.
    self.modes = [] # All modes defined here or by imports.
#@+node:ekr.20060824111500.164: *5* helpers
#@+node:ekr.20060824111500.165: *6* attrsToList
def attrsToList (self,attrs):

    '''Convert the attributes to a list of g.Bunches.

    attrs: an Attributes item passed to startElement.

    sep: the separator charater between attributes.'''

    return [
        g.Bunch(name=name,val=attrs.getValue(name))
        for name in attrs.getNames()
    ]
#@+node:ekr.20060824111500.166: *6* attrsToString
def attrsToString (self,attrs,sep='\n'):

    '''Convert the attributes to a string.

    attrs: an Attributes item passed to startElement.

    sep: the separator charater between attributes.'''

    result = [
        '%s="%s"' % (bunch.name,bunch.val)
        for bunch in self.attrsToList(attrs)
    ]

    return sep.join(result)
#@+node:ekr.20060824111500.167: *6* clean
def clean(self,s):

    return g.toEncodedString(s,"ascii")
#@+node:ekr.20060824111500.168: *6* error
def error (self, message):

    print()
    print()
    print('XML error: %s' % (message))
    print()

    self.errors += 1
#@+node:ekr.20060824111500.169: *6* printStartElement
def printStartElement(self,name,attrs):

    if attrs.getLength() > 0:
        print('<%s %s>' % (
            self.clean(name).strip(),
            self.attrsToString(attrs,sep=' '))) ## ,
    else:
        print('<%s>' % (self.clean(name).strip())) ## ,

    if name.lower() in self.newLineAfterStartElement:
        print()
#@+node:ekr.20060824111500.170: *6* printSummary
def printSummary (self):

    print('Summary...')
    print('-' * 10, 'non- mode statistics')
    print('modes',len(self.modes))
    print('elements', self.numberOfElements)
#@+node:ekr.20060824111500.171: *5* sax over-rides
#@+node:ekr.20060824111500.172: *6*  Do nothing...
#@+node:ekr.20060824111500.173: *7* other methods
def ignorableWhitespace(self):
    g.trace()

def processingInstruction (self,target,data):
    g.trace()

def skippedEntity(self,name):
    g.trace(name)

def startElementNS(self,name,qname,attrs):
    g.trace(name)

def endElementNS(self,name,qname):
    g.trace(name)
#@+node:ekr.20060824111500.174: *7* endDocument
def endDocument(self):

    pass


#@+node:ekr.20060824111500.175: *7* startDocument
def startDocument(self):

    pass
#@+node:ekr.20060824111500.176: *6* characters
def characters(self,content):

    # content = content.replace('\r','').strip()
    content = content.replace('\r','')
    if content.strip(): content = content.strip()
    content = self.clean(content)

    elementName = self.elementStack and self.elementStack[-1].lower() or '<no element name>'

    if self.printAllElements:
        print(content) ## ,
    elif self.printCharacters and content and elementName not in self.suppressContent:
        print('content:',elementName,repr(content))

    if self.mode:
        self.mode.doContent(elementName,content)
    else:
        self.error('characters outside of mode')
#@+node:ekr.20060824111500.177: *6* endElement
def endElement(self,name):

    self.doEndElement(name)

    name2 = self.elementStack.pop()
    assert name == name2
#@+node:ekr.20060824111500.178: *6* startElement
def startElement(self,name,attrs):

    if self.mode:
        self.mode.numberOfElements += 1
    else:
        self.numberOfElements += 1

    self.elementStack.append(name)
    self.doStartElement(name,attrs)
#@+node:ekr.20060824111500.179: *5* doStartElement
def doStartElement (self,elementName,attrs):

    if self.printAllElements or elementName.lower() in self.printElements:
        self.printStartElement(elementName,attrs)

    elementName = elementName.lower()

    if elementName == 'mode':
        if self.mode:
            self.error('Multiple modes')
        else:
            self.mode = modeClass(self,self.inputFileName)
    elif self.mode:
        self.mode.startElement(elementName)
        for bunch in self.attrsToList(attrs):
            if self.printAttributes:
                print('attr:',elementName,bunch.name,'=',bunch.val)
            self.mode.doAttribute(bunch.name,bunch.val)
    else:
        self.error('Start element appears outside of Mode:%s' % elementName)
        for bunch in self.attrsToList(attrs):
            self.error('Attribute appears outside of Mode:%s' % bunch.name)
#@+node:ekr.20060824111500.180: *5* doEndElement
def doEndElement (self,elementName):

    if self.printAllElements or elementName.lower() in self.printElements:
        print('</' + self.clean(elementName).strip() + '>')

    if elementName.lower() == 'mode':
        if opt_print_summary:
            self.mode.printSummary()
    elif self.mode:
        self.mode.endElement(elementName)
    else:
        self.error('End element appears outside of Mode:%s' % elementName)
        # for bunch in self.attrsToList(attrs):
        #    self.error('Attribute appears outside of Mode:%s' %bunch.name)
#@+node:ekr.20060824111500.181: *5* getMode
def getMode (self):

    if self.errors:
        return None
    else:
        return self.mode
#@+node:ekr.20040714055306: *3* elispToPy
#@+node:ekr.20071104221525.1: *4* The project is doomed
@language lisp
@nocolor
@

This project is doomed.  It would take AI (lots of special cases) to
translate elisp to readable Python.

Furthermore, how are we to simulate the semantics of lisp constructs
such as interactive, fboundp, setcdr and nconc?

For example, the following is going to cause all kinds of semantic problems.
@c

@color

(defun dired-do-igrep (program expression &optional options arg)
  "*Run `grep` on the marked (or next prefix ARG) files.
See `\\[igrep]'."

  (interactive
   (let ((igrep-args
	  (let ((current-prefix-arg nil))
	    (igrep-read-args t))))
     ;; Delete FILES:
     (setcdr (nthcdr 1 igrep-args) (nthcdr 3 igrep-args))
     ;; Append ARG:
     (nconc igrep-args (list current-prefix-arg))))

  (igrep program
	 expression
	 (funcall (cond ((fboundp 'dired-get-marked-files) ; GNU Emacs
			 'dired-get-marked-files)
			((fboundp 'dired-mark-get-files) ; XEmacs
			 'dired-mark-get-files))
		  t arg)
	 options))

; The following is not going to be easy to translate!

(defun spam (prompt)
    (if cond
        then-part
        else-part1
        else-part2
    )
)

@nocolor
# There is no *simple* tranlation of the above to Python!

def spam (prompt):
    return  # need ternary operator!

# Need complex code generators!
def spam (prompt):
    if cond:
        return then-part
    else:
        else-part1
        return else-part2


#@+node:ekr.20040713123617.1: *4* older e2pyScript
@language python
@tabwidth -4

import string

tabWidth = 4 # how many blanks in a tab.
printFlag = False
doLeoTranslations,dontDoLeoTranslations = True,False

gClassName = "" # The class name for the present function.  Used to modify ivars.
gIvars = [] # List of ivars to be converted to self.ivar

@others

run(c)
#@+node:ekr.20050220085042.1: *5* run
def run (c):

    import leoTest
    u = leoTest.testUtils() 
    input  = u.findNodeAnywhere(c,'-elisp2py-input-')
    output = u.findNodeAnywhere(c,'-elisp2py-output-')
    assert input and output

    print ; print '*' * 60
    e = e2py(dumping=False)
    for p in input.children():
        print ; print '-' * 10, p.h
        print p.b
        result = e.doOuterBlock(p.b)
        print '-' * 20
        print result
#@+node:ekr.20050220091046: *5* class e2py
class e2py:

    '''A text-based (not token-based) approach to parsing.'''

    @others
#@+node:ekr.20050220091046.1: *6* ctor
def __init__ (self,dumping=False):

    self.dumping = dumping
#@+node:ekr.20050220111049: *6* doBlock
def doBlock (self,s,strip=True):

    '''Handle an outer block or a formerly parenthesized block.'''

    i = 0 ; result = []
    while i < len(s):
        j = s.find('(',i)
        if j == -1:
            tail = s[i:]
            if tail:
                # g.trace(repr(tail))
                result.extend(g.splitLines(tail))
            break
        else:
            prev = s[i:j]
            if prev: result.extend(g.splitLines(prev))
            i = j
            n,ok = self.findMatchingBracket(s[i:])
            if ok:
                block = s[i:i+n]
                block = self.stripLws(block)
                block_result = self.doParenBlock(block)
                if block_result:
                    result.extend(block_result)
            i += n

    result = self.removeBlankLines(result)
    self.dump(result)
    return result
#@+node:ekr.20050220091046.3: *6* doParenBlock
def doParenBlock (self,s):

    ''' - Strip outer parens.
        - Call doBlock recursively for all inner parens.
        - Add one level of indentation to each line.'''
    n,ok = self.findMatchingBracket(s)
    assert n == len(s) and ok
    s = s[1:-1] # Strip the brackets
    i = 0
    i = g.skip_ws(s,i)
    j,id = self.skip_id(s,i)
    if id:
        s = s[j:].lstrip()
        if id == 'defun': result = self.doDef(s,id)
        elif id == 'let': result = self.doLet(s)
        elif id == 'if': result = self.doIf(s)
        elif id in ('prog1','progn'): result = self.doProg(s,id)
        else: result = self.doId(s,id)
    elif s[i] == '(':
        s = s[i:].lstrip()
        result = self.doDoubleParen(s)
    else: result = self.doBlock(s)

    self.dump(result)
    return result
#@+node:ekr.20050220105058: *6* doDef
def doDef(self,s,id):
    # g.trace(id)
    if id == 'defun':
        kind,i,j,name = self.getToken(s,0)
        if kind == 'id':
            kind,i,j,params = self.getToken(s,j)
            if kind == '()':
                s = s[j:]
                result = ['def %s %s:' % (name,params)]
                result.extend(self.indent(self.doBlock(s)))
                self.dump(result)
                return result

    # Fall through if error.
    result = [id]
    result.extend(self.indent(self.doBlock(s)))
    self.dump(result)
    return result
#@+node:ekr.20050220124658: *6* doDoubleParen
def doDoubleParen (self,s):

    n,ok = self.findMatchingBracket(s)
    if ok:
        s2 = s[:n] ; s3 = s[n:]
        result = ['(:']
        result.extend(self.indent(self.doParenBlock(s2)))
        result.extend([':)'])
        result.extend(self.doBlock(s3))
    else:
        result = self.doBlock(s)

    self.dump(result)
    return result
#@+node:ekr.20050220111114: *6* doId
def doId(self,s,id):

    # g.trace(id)
    if 1:
        result = ['(%s:' % id]
        result.extend(self.indent(self.doBlock(s)))
        result.extend([':%s)' % id])
    else:
        result = [id]
        result.extend(self.doBlock(s))

    self.dump(result)
    return result
#@+node:ekr.20050220105058.1: *6* doIf
def doIf(self,s):

    # g.trace()

    if 1:
        result = ['if:']
        result.extend(self.indent(self.doBlock(s)))
        result.extend([':if'])

    else: # not yet.  Don't suck everything into the 'if' statement!
        block = self.doBlock(s)
        result = ['if (%s):' % ' '.join(block)]

    self.dump(result)
    return result
#@+node:ekr.20050220105058.2: *6* doLet
def doLet(self,s):

    # g.trace()

    result = ['let:']
    result.extend(self.indent(self.doBlock(s)))
    result.extend([':let'])

    self.dump(result)
    return result
#@+node:ekr.20050220091046.2: *6* doOuterBlock
def doOuterBlock (self,s):

    '''Handle outermost code.  Return a string, not a list.'''

    s = self.stripLws(s)
    result = self.doBlock(s)
    result = self.removeBlankLines(result)
    return '\n'.join(result)
#@+node:ekr.20050220105058.3: *6* doProg
def doProg(self,s,id):

    # g.trace(id)

    result = [id]
    result.extend(self.indent(self.doBlock(s)))

    self.dump(result)
    return result
#@+node:ekr.20050220111923: *6* dump
def dump(self,lines):

    if self.dumping:
        print '%s returns...' % g.callerName(2)
        lines = [str(line) for line in lines]
        print g.listToString(lines)
#@+node:ekr.20050220092732: *6* findMatchingBracket
def findMatchingBracket(self,s,i=0):

    ch1 = s[i]
    assert ch1 in "({["
    delim = self.matchingBracket(ch1)
    level = 1
    for ch in s[i+1:]:
        i += 1
        # g.trace(level,ch)
        if ch == ch1:
            level += 1
        elif ch == delim:
            level -= 1
            if level == 0: return i+1,True
    print "%s not matched by %s in %s" % (ch1,delim,s)
    return len(s),False
#@+node:ekr.20050220114616: *6* getToken
def getToken (self,s,i=0):

    i = g.skip_ws(s,i)
    if i < len(s):
        ch = s[i]
        if ch == '"':
            j = self.skipString(s,i)
            val = '"',i,j,s[i:j]
        elif ch in string.ascii_letters or ch in string.digits or ch in '-_':
            j,name = self.skip_id(s,i)
            val = 'id',i,j,name
        elif ch == '(':
            j,ok = self.findMatchingBracket(s,i)
            if ok:
                val = '()',i,j,s[i:j]
            else:
                val = '(',i,i,'('
        else:
            val = ch,i,i,ch
    else:
        val = None,i,i,None

    # g.trace(repr(s[i]),val)
    return val
#@+node:ekr.20050220105726: *6* indent
def indent (self,lines,strip=True):

    '''Add a tab to each element of a list.'''

    return ['    ' + line for line in lines if not strip or line.strip()]
#@+node:ekr.20050220093752: *6* matchingBracket
def matchingBracket (self,ch):

    assert ch in "({["

    if   ch == '(': return ')'
    elif ch == '{': return '}'
    else:           return ']'
#@+node:ekr.20050220103808: *6* skip_id
def skip_id(self,s,i=0):

    j = g.skip_id(s,i,chars='-')
    id = s[i:j]
    return j,id
#@+node:ekr.20050220105058.4: *6* skipString
def skipString(self,s,i):

    # Skip the opening double quote.
    i1 = i
    ch = s[i]
    i += 1
    assert(ch == '"')

    while i < len(s):
        ch = s[i]
        i += 1
        if ch == '"': return i
        elif ch == '\\': i += 1

    print "run-on elisp string: %s" % g.get_line(s[i1:])
    return i
#@+node:ekr.20050220122447: *6* removeBlankLines
def removeBlankLines (self,lines):

    return [line for line in lines if line.strip()]
#@+node:ekr.20050220100049: *6* stripLws
def stripLws(self,s):

    lines = g.splitLines(s)
    result = [line.lstrip() for line in lines]
    return ''.join(result)
#@+node:ekr.20071104221525: *4* newer elispToPy
#@+node:ekr.20071104221525.89: *5* new-elisp2py-input-
@language lisp
#@+node:ekr.20071104221525.90: *6* if-test
@language lisp

; What would the translation of this be???

; def (spam):
;   '''docstring'''
;   return ?? # A python if statement can't be used here!

(defun spam (prompt)
    "docstring"
    (if igrep-insert-default-key
        (define-key minibuffer)
        (do-else2)
        'do-else1
    )
)

#@+node:ekr.20071104221525.91: *5* new-elisp2py-output-
#@+node:ekr.20060824111500: *3* jEdit2Py stuff
#@+node:ekr.20060824111500.10: *4* Colorizer tests
#@+node:ekr.20060824111500.11: *5* C
@color
@language c

#include abc

// tests

/* test
end of test. */

for (i = 1; i < 6; i++) {
    continue
#@+node:ekr.20060824111500.12: *5* Python
@color

<< test >>

@doc test
another line

@c

'test'

'''test'''

# @nocolor

for i = 1;
    yield
#@+node:ekr.20060824111500.13: *5* php.py
# Leo colorizer control file for php mode.
# This file is in the public domain.

# Properties for php mode.
properties = {
	"commentEnd": "-->",
	"commentStart": "<!--",
	"indentCloseBrackets": "}",
	"indentOpenBrackets": "{",
	"lineUpClosingBracket": "true",
}

# Attributes dict for php_main ruleset.
php_main_attributes_dict = {
	"default": "null",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Attributes dict for php_tags ruleset.
php_tags_attributes_dict = {
	"default": "MARKUP",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Attributes dict for php_tags_literal ruleset.
php_tags_literal_attributes_dict = {
	"default": "LITERAL1",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Attributes dict for php_php ruleset.
php_php_attributes_dict = {
	"default": "LITERAL1",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Attributes dict for php_php_literal ruleset.
php_php_literal_attributes_dict = {
	"default": "LITERAL1",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Attributes dict for php_javascript ruleset.
php_javascript_attributes_dict = {
	"default": "MARKUP",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Attributes dict for php_javascript_php ruleset.
php_javascript_php_attributes_dict = {
	"default": "MARKUP",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Attributes dict for php_phpdoc ruleset.
php_phpdoc_attributes_dict = {
	"default": "COMMENT3",
	"digit_re": "",
	"highlight_digits": "true",
	"ignore_case": "true",
	"no_word_sep": "",
}

# Dictionary of attributes dictionaries for php mode.
attributesDictDict = {
	"php_javascript": php_javascript_attributes_dict,
	"php_javascript_php": php_javascript_php_attributes_dict,
	"php_main": php_main_attributes_dict,
	"php_php": php_php_attributes_dict,
	"php_php_literal": php_php_literal_attributes_dict,
	"php_phpdoc": php_phpdoc_attributes_dict,
	"php_tags": php_tags_attributes_dict,
	"php_tags_literal": php_tags_literal_attributes_dict,
}

# Keywords dict for php_main ruleset.
php_main_keywords_dict = {}

# Keywords dict for php_tags ruleset.
php_tags_keywords_dict = {}

# Keywords dict for php_tags_literal ruleset.
php_tags_literal_keywords_dict = {}

# Keywords dict for php_php ruleset.
php_php_keywords_dict = {
	"COM_invoke": "keyword2",
	"COM_load": "keyword2",
	"__CLASS__": "keyword3",
	"__FILE__": "keyword3",
	"__FUNCTION__": "keyword3",
	"__LINE__": "keyword3",
	"__METHOD__": "keyword3",
	"abs": "keyword2",
	"abstract": "keyword1",
	"accept_connect": "keyword2",
	"acos": "keyword2",
	"add": "keyword2",
	"add_iovec": "keyword2",
	"addaction": "keyword2",
	"addcolor": "keyword2",
	"addcslashes": "keyword2",
	"addentry": "keyword2",
	"addfill": "keyword2",
	"addshape": "keyword2",
	"addslashes": "keyword2",
	"addstring": "keyword2",
	"align": "keyword2",
	"and": "operator",
	"apache_child_terminate": "keyword2",
	"apache_lookup_uri": "keyword2",
	"apache_note": "keyword2",
	"apache_sub_req": "keyword2",
	"array": "keyword1",
	"array_combine": "keyword2",
	"array_count_values": "keyword2",
	"array_diff": "keyword2",
	"array_diff_assoc": "keyword2",
	"array_diff_uassoc": "keyword2",
	"array_filter": "keyword2",
	"array_flip": "keyword2",
	"array_intersect": "keyword2",
	"array_intersect_assoc": "keyword2",
	"array_keys": "keyword2",
	"array_map": "keyword2",
	"array_merge": "keyword2",
	"array_merge_recursive": "keyword2",
	"array_multisort": "keyword2",
	"array_pad": "keyword2",
	"array_pop": "keyword2",
	"array_push": "keyword2",
	"array_rand": "keyword2",
	"array_reduce": "keyword2",
	"array_reverse": "keyword2",
	"array_search": "keyword2",
	"array_shift": "keyword2",
	"array_slice": "keyword2",
	"array_splice": "keyword2",
	"array_sum": "keyword2",
	"array_udiff": "keyword2",
	"array_udiff_assoc": "keyword2",
	"array_udiff_uassoc": "keyword2",
	"array_unique": "keyword2",
	"array_unshift": "keyword2",
	"array_values": "keyword2",
	"array_walk": "keyword2",
	"array_walk_recursive": "keyword2",
	"arsort": "keyword2",
	"as": "keyword1",
	"asin": "keyword2",
	"asort": "keyword2",
	"aspell_check": "keyword2",
	"aspell_check_raw": "keyword2",
	"aspell_new": "keyword2",
	"aspell_suggest": "keyword2",
	"assert": "keyword2",
	"assert_options": "keyword2",
	"atan": "keyword2",
	"atan2": "keyword2",
	"base64_decode": "keyword2",
	"base64_encode": "keyword2",
	"base_convert": "keyword2",
	"basename": "keyword2",
	"bcadd": "keyword2",
	"bccomp": "keyword2",
	"bcdiv": "keyword2",
	"bcmod": "keyword2",
	"bcmul": "keyword2",
	"bcpow": "keyword2",
	"bcscale": "keyword2",
	"bcsqrt": "keyword2",
	"bcsub": "keyword2",
	"bin2hex": "keyword2",
	"bind": "keyword2",
	"bindec": "keyword2",
	"bindtextdomain": "keyword2",
	"break": "keyword1",
	"build_iovec": "keyword2",
	"bzclose": "keyword2",
	"bzcompress": "keyword2",
	"bzdecompress": "keyword2",
	"bzerrno": "keyword2",
	"bzerror": "keyword2",
	"bzerrstr": "keyword2",
	"bzflush": "keyword2",
	"bzopen": "keyword2",
	"bzread": "keyword2",
	"bzwrite": "keyword2",
	"call_user_func": "keyword2",
	"call_user_func_array": "keyword2",
	"call_user_method": "keyword2",
	"call_user_method_array": "keyword2",
	"case": "keyword1",
	"catch": "keyword1",
	"ccvs_add": "keyword2",
	"ccvs_auth": "keyword2",
	"ccvs_command": "keyword2",
	"ccvs_count": "keyword2",
	"ccvs_delete": "keyword2",
	"ccvs_done": "keyword2",
	"ccvs_init": "keyword2",
	"ccvs_lookup": "keyword2",
	"ccvs_new": "keyword2",
	"ccvs_report": "keyword2",
	"ccvs_return": "keyword2",
	"ccvs_reverse": "keyword2",
	"ccvs_sale": "keyword2",
	"ccvs_status": "keyword2",
	"ccvs_textvalue": "keyword2",
	"ccvs_void": "keyword2",
	"ceil": "keyword2",
	"chdir": "keyword2",
	"checkdate": "keyword2",
	"checkdnsrr": "keyword2",
	"chgrp": "keyword2",
	"chmod": "keyword2",
	"chop": "keyword2",
	"chown": "keyword2",
	"chr": "keyword2",
	"chroot": "keyword2",
	"chunk_split": "keyword2",
	"class": "keyword1",
	"class_exists": "keyword2",
	"clearstatcache": "keyword2",
	"clone": "keyword1",
	"close": "keyword2",
	"closedir": "keyword2",
	"closelog": "keyword2",
	"com_get": "keyword2",
	"com_propget": "keyword2",
	"com_propput": "keyword2",
	"com_propset": "keyword2",
	"com_set": "keyword2",
	"compact": "keyword2",
	"confirm_cybermut_compiled": "keyword2",
	"confirm_extname_compiled": "keyword2",
	"connect": "keyword2",
	"connection_aborted": "keyword2",
	"connection_status": "keyword2",
	"const": "keyword1",
	"constant": "keyword2",
	"continue": "keyword1",
	"convert_cyr_string": "keyword2",
	"convert_uudecode": "keyword2",
	"convert_uuencode": "keyword2",
	"copy": "keyword2",
	"cos": "keyword2",
	"count": "keyword2",
	"count_chars": "keyword2",
	"cpdf_add_annotation": "keyword2",
	"cpdf_add_outline": "keyword2",
	"cpdf_arc": "keyword2",
	"cpdf_begin_text": "keyword2",
	"cpdf_circle": "keyword2",
	"cpdf_clip": "keyword2",
	"cpdf_close": "keyword2",
	"cpdf_closepath": "keyword2",
	"cpdf_closepath_fill_stroke": "keyword2",
	"cpdf_closepath_stroke": "keyword2",
	"cpdf_continue_text": "keyword2",
	"cpdf_curveto": "keyword2",
	"cpdf_end_text": "keyword2",
	"cpdf_fill": "keyword2",
	"cpdf_fill_stroke": "keyword2",
	"cpdf_finalize": "keyword2",
	"cpdf_finalize_page": "keyword2",
	"cpdf_global_set_document_limits": "keyword2",
	"cpdf_import_jpeg": "keyword2",
	"cpdf_lineto": "keyword2",
	"cpdf_moveto": "keyword2",
	"cpdf_newpath": "keyword2",
	"cpdf_open": "keyword2",
	"cpdf_output_buffer": "keyword2",
	"cpdf_page_init": "keyword2",
	"cpdf_place_inline_image": "keyword2",
	"cpdf_rect": "keyword2",
	"cpdf_restore": "keyword2",
	"cpdf_rlineto": "keyword2",
	"cpdf_rmoveto": "keyword2",
	"cpdf_rotate": "keyword2",
	"cpdf_rotate_text": "keyword2",
	"cpdf_save": "keyword2",
	"cpdf_save_to_file": "keyword2",
	"cpdf_scale": "keyword2",
	"cpdf_set_action_url": "keyword2",
	"cpdf_set_char_spacing": "keyword2",
	"cpdf_set_creator": "keyword2",
	"cpdf_set_current_page": "keyword2",
	"cpdf_set_font": "keyword2",
	"cpdf_set_font_directories": "keyword2",
	"cpdf_set_font_map_file": "keyword2",
	"cpdf_set_horiz_scaling": "keyword2",
	"cpdf_set_keywords": "keyword2",
	"cpdf_set_leading": "keyword2",
	"cpdf_set_page_animation": "keyword2",
	"cpdf_set_subject": "keyword2",
	"cpdf_set_text_matrix": "keyword2",
	"cpdf_set_text_pos": "keyword2",
	"cpdf_set_text_rendering": "keyword2",
	"cpdf_set_text_rise": "keyword2",
	"cpdf_set_title": "keyword2",
	"cpdf_set_viewer_preferences": "keyword2",
	"cpdf_set_word_spacing": "keyword2",
	"cpdf_setdash": "keyword2",
	"cpdf_setflat": "keyword2",
	"cpdf_setgray": "keyword2",
	"cpdf_setgray_fill": "keyword2",
	"cpdf_setgray_stroke": "keyword2",
	"cpdf_setlinecap": "keyword2",
	"cpdf_setlinejoin": "keyword2",
	"cpdf_setlinewidth": "keyword2",
	"cpdf_setmiterlimit": "keyword2",
	"cpdf_setrgbcolor": "keyword2",
	"cpdf_setrgbcolor_fill": "keyword2",
	"cpdf_setrgbcolor_stroke": "keyword2",
	"cpdf_show": "keyword2",
	"cpdf_show_xy": "keyword2",
	"cpdf_stringwidth": "keyword2",
	"cpdf_stroke": "keyword2",
	"cpdf_text": "keyword2",
	"cpdf_translate": "keyword2",
	"crack_check": "keyword2",
	"crack_closedict": "keyword2",
	"crack_getlastmessage": "keyword2",
	"crack_opendict": "keyword2",
	"crash": "keyword2",
	"crc32": "keyword2",
	"create_function": "keyword2",
	"crypt": "keyword2",
	"ctype_alnum": "keyword2",
	"ctype_alpha": "keyword2",
	"ctype_cntrl": "keyword2",
	"ctype_digit": "keyword2",
	"ctype_graph": "keyword2",
	"ctype_lower": "keyword2",
	"ctype_print": "keyword2",
	"ctype_punct": "keyword2",
	"ctype_space": "keyword2",
	"ctype_upper": "keyword2",
	"ctype_xdigit": "keyword2",
	"curl_close": "keyword2",
	"curl_errno": "keyword2",
	"curl_error": "keyword2",
	"curl_exec": "keyword2",
	"curl_getinfo": "keyword2",
	"curl_init": "keyword2",
	"curl_setopt": "keyword2",
	"curl_version": "keyword2",
	"current": "keyword2",
	"cv_add": "keyword2",
	"cv_auth": "keyword2",
	"cv_command": "keyword2",
	"cv_count": "keyword2",
	"cv_delete": "keyword2",
	"cv_done": "keyword2",
	"cv_init": "keyword2",
	"cv_lookup": "keyword2",
	"cv_new": "keyword2",
	"cv_report": "keyword2",
	"cv_return": "keyword2",
	"cv_reverse": "keyword2",
	"cv_sale": "keyword2",
	"cv_status": "keyword2",
	"cv_textvalue": "keyword2",
	"cv_void": "keyword2",
	"cybercash_base64_decode": "keyword2",
	"cybercash_base64_encode": "keyword2",
	"cybercash_decr": "keyword2",
	"cybercash_encr": "keyword2",
	"cybermut_creerformulairecm": "keyword2",
	"cybermut_creerreponsecm": "keyword2",
	"cybermut_testmac": "keyword2",
	"date": "keyword2",
	"dba_close": "keyword2",
	"dba_delete": "keyword2",
	"dba_exists": "keyword2",
	"dba_fetch": "keyword2",
	"dba_firstkey": "keyword2",
	"dba_insert": "keyword2",
	"dba_nextkey": "keyword2",
	"dba_open": "keyword2",
	"dba_optimize": "keyword2",
	"dba_popen": "keyword2",
	"dba_replace": "keyword2",
	"dba_sync": "keyword2",
	"dbase_add_record": "keyword2",
	"dbase_close": "keyword2",
	"dbase_create": "keyword2",
	"dbase_delete_record": "keyword2",
	"dbase_get_record": "keyword2",
	"dbase_get_record_with_names": "keyword2",
	"dbase_numfields": "keyword2",
	"dbase_numrecords": "keyword2",
	"dbase_open": "keyword2",
	"dbase_pack": "keyword2",
	"dbase_replace_record": "keyword2",
	"dblist": "keyword2",
	"dbmclose": "keyword2",
	"dbmdelete": "keyword2",
	"dbmexists": "keyword2",
	"dbmfetch": "keyword2",
	"dbmfirstkey": "keyword2",
	"dbminsert": "keyword2",
	"dbmnextkey": "keyword2",
	"dbmopen": "keyword2",
	"dbmreplace": "keyword2",
	"dbx_close": "keyword2",
	"dbx_cmp_asc": "keyword2",
	"dbx_cmp_desc": "keyword2",
	"dbx_connect": "keyword2",
	"dbx_error": "keyword2",
	"dbx_query": "keyword2",
	"dbx_sort": "keyword2",
	"dcgettext": "keyword2",
	"debug_backtrace": "keyword2",
	"debug_print_backtrace": "keyword2",
	"decbin": "keyword2",
	"dechex": "keyword2",
	"declare": "keyword1",
	"decoct": "keyword2",
	"default": "keyword1",
	"define": "keyword2",
	"define_syslog_variables": "keyword2",
	"defined": "keyword2",
	"deg2rad": "keyword2",
	"delete_iovec": "keyword2",
	"dgettext": "keyword2",
	"die": "keyword2",
	"dir": "keyword2",
	"dirname": "keyword2",
	"diskfreespace": "keyword2",
	"display_disabled_function": "keyword2",
	"dl": "keyword2",
	"do": "keyword1",
	"domxml_add_root": "keyword2",
	"domxml_attributes": "keyword2",
	"domxml_children": "keyword2",
	"domxml_dumpmem": "keyword2",
	"domxml_elem_get_attribute": "keyword2",
	"domxml_elem_set_attribute": "keyword2",
	"domxml_get_attribute": "keyword2",
	"domxml_getattr": "keyword2",
	"domxml_new_child": "keyword2",
	"domxml_new_xmldoc": "keyword2",
	"domxml_node": "keyword2",
	"domxml_node_attributes": "keyword2",
	"domxml_node_children": "keyword2",
	"domxml_node_new_child": "keyword2",
	"domxml_node_set_content": "keyword2",
	"domxml_node_unlink_node": "keyword2",
	"domxml_root": "keyword2",
	"domxml_set_attribute": "keyword2",
	"domxml_setattr": "keyword2",
	"domxml_unlink_node": "keyword2",
	"domxml_version": "keyword2",
	"doubleval": "keyword2",
	"drawarc": "keyword2",
	"drawcircle": "keyword2",
	"drawcubic": "keyword2",
	"drawcubicto": "keyword2",
	"drawcurve": "keyword2",
	"drawcurveto": "keyword2",
	"drawglyph": "keyword2",
	"drawline": "keyword2",
	"drawlineto": "keyword2",
	"each": "keyword2",
	"easter_date": "keyword2",
	"easter_days": "keyword2",
	"echo": "keyword1",
	"else": "keyword1",
	"elseif": "keyword1",
	"empty": "keyword1",
	"end": "keyword2",
	"endfor": "keyword1",
	"endforeach": "keyword1",
	"endif": "keyword1",
	"endswitch": "keyword1",
	"endwhile": "keyword1",
	"ereg": "keyword2",
	"ereg_replace": "keyword2",
	"eregi": "keyword2",
	"eregi_replace": "keyword2",
	"error_log": "keyword2",
	"error_reporting": "keyword2",
	"escapeshellarg": "keyword2",
	"escapeshellcmd": "keyword2",
	"exec": "keyword2",
	"exit": "keyword2",
	"exp": "keyword2",
	"explode": "keyword2",
	"extends": "keyword1",
	"extension_loaded": "keyword2",
	"extract": "keyword2",
	"ezmlm_hash": "keyword2",
	"false": "keyword3",
	"fbsql": "keyword2",
	"fbsql_affected_rows": "keyword2",
	"fbsql_autocommit": "keyword2",
	"fbsql_close": "keyword2",
	"fbsql_commit": "keyword2",
	"fbsql_connect": "keyword2",
	"fbsql_create_db": "keyword2",
	"fbsql_data_seek": "keyword2",
	"fbsql_database": "keyword2",
	"fbsql_database_password": "keyword2",
	"fbsql_db_query": "keyword2",
	"fbsql_drop_db": "keyword2",
	"fbsql_errno": "keyword2",
	"fbsql_error": "keyword2",
	"fbsql_fetch_array": "keyword2",
	"fbsql_fetch_assoc": "keyword2",
	"fbsql_fetch_field": "keyword2",
	"fbsql_fetch_lengths": "keyword2",
	"fbsql_fetch_object": "keyword2",
	"fbsql_fetch_row": "keyword2",
	"fbsql_field_flags": "keyword2",
	"fbsql_field_len": "keyword2",
	"fbsql_field_name": "keyword2",
	"fbsql_field_seek": "keyword2",
	"fbsql_field_table": "keyword2",
	"fbsql_field_type": "keyword2",
	"fbsql_free_result": "keyword2",
	"fbsql_hostname": "keyword2",
	"fbsql_insert_id": "keyword2",
	"fbsql_list_dbs": "keyword2",
	"fbsql_list_fields": "keyword2",
	"fbsql_list_tables": "keyword2",
	"fbsql_next_result": "keyword2",
	"fbsql_num_fields": "keyword2",
	"fbsql_num_rows": "keyword2",
	"fbsql_password": "keyword2",
	"fbsql_pconnect": "keyword2",
	"fbsql_query": "keyword2",
	"fbsql_result": "keyword2",
	"fbsql_rollback": "keyword2",
	"fbsql_select_db": "keyword2",
	"fbsql_start_db": "keyword2",
	"fbsql_stop_db": "keyword2",
	"fbsql_username": "keyword2",
	"fbsql_warnings": "keyword2",
	"fclose": "keyword2",
	"fd_alloc": "keyword2",
	"fd_clear": "keyword2",
	"fd_dealloc": "keyword2",
	"fd_isset": "keyword2",
	"fd_set": "keyword2",
	"fd_zero": "keyword2",
	"fdf_add_template": "keyword2",
	"fdf_close": "keyword2",
	"fdf_create": "keyword2",
	"fdf_get_file": "keyword2",
	"fdf_get_status": "keyword2",
	"fdf_get_value": "keyword2",
	"fdf_next_field_name": "keyword2",
	"fdf_open": "keyword2",
	"fdf_save": "keyword2",
	"fdf_set_ap": "keyword2",
	"fdf_set_file": "keyword2",
	"fdf_set_flags": "keyword2",
	"fdf_set_javascript_action": "keyword2",
	"fdf_set_opt": "keyword2",
	"fdf_set_status": "keyword2",
	"fdf_set_submit_form_action": "keyword2",
	"fdf_set_value": "keyword2",
	"feof": "keyword2",
	"fetch_iovec": "keyword2",
	"fflush": "keyword2",
	"fgetc": "keyword2",
	"fgetcsv": "keyword2",
	"fgets": "keyword2",
	"fgetss": "keyword2",
	"file": "keyword2",
	"file_exists": "keyword2",
	"file_get_contents": "keyword2",
	"file_put_contents": "keyword2",
	"fileatime": "keyword2",
	"filectime": "keyword2",
	"filegroup": "keyword2",
	"fileinode": "keyword2",
	"filemtime": "keyword2",
	"fileowner": "keyword2",
	"fileperms": "keyword2",
	"filepro": "keyword2",
	"filepro_fieldcount": "keyword2",
	"filepro_fieldname": "keyword2",
	"filepro_fieldtype": "keyword2",
	"filepro_fieldwidth": "keyword2",
	"filepro_retrieve": "keyword2",
	"filepro_rowcount": "keyword2",
	"filesize": "keyword2",
	"filetype": "keyword2",
	"final": "keyword1",
	"floatval": "keyword2",
	"flock": "keyword2",
	"floor": "keyword2",
	"flush": "keyword2",
	"fopen": "keyword2",
	"fopenstream": "keyword2",
	"for": "keyword1",
	"foreach": "keyword1",
	"fpassthru": "keyword2",
	"fputs": "keyword2",
	"fread": "keyword2",
	"free_iovec": "keyword2",
	"frenchtojd": "keyword2",
	"fribidi_log2vis": "keyword2",
	"fscanf": "keyword2",
	"fseek": "keyword2",
	"fsockopen": "keyword2",
	"fstat": "keyword2",
	"ftell": "keyword2",
	"ftp_alloc": "keyword2",
	"ftp_cdup": "keyword2",
	"ftp_chdir": "keyword2",
	"ftp_connect": "keyword2",
	"ftp_delete": "keyword2",
	"ftp_exec": "keyword2",
	"ftp_fget": "keyword2",
	"ftp_fput": "keyword2",
	"ftp_get": "keyword2",
	"ftp_login": "keyword2",
	"ftp_mdtm": "keyword2",
	"ftp_mkdir": "keyword2",
	"ftp_nlist": "keyword2",
	"ftp_pasv": "keyword2",
	"ftp_put": "keyword2",
	"ftp_pwd": "keyword2",
	"ftp_quit": "keyword2",
	"ftp_rawlist": "keyword2",
	"ftp_rename": "keyword2",
	"ftp_rmdir": "keyword2",
	"ftp_site": "keyword2",
	"ftp_size": "keyword2",
	"ftp_ssl_connect": "keyword2",
	"ftp_systype": "keyword2",
	"ftruncate": "keyword2",
	"func_get_arg": "keyword2",
	"func_get_args": "keyword2",
	"func_num_args": "keyword2",
	"function": "keyword1",
	"function_exists": "keyword2",
	"fwrite": "keyword2",
	"gd_info": "keyword2",
	"get_all_headers": "keyword2",
	"get_browser": "keyword2",
	"get_cfg_var": "keyword2",
	"get_class": "keyword2",
	"get_class_methods": "keyword2",
	"get_class_vars": "keyword2",
	"get_current_user": "keyword2",
	"get_declared_classes": "keyword2",
	"get_declared_interfaces": "keyword2",
	"get_defined_functions": "keyword2",
	"get_defined_vars": "keyword2",
	"get_extension_funcs": "keyword2",
	"get_headers": "keyword2",
	"get_html_translation_table": "keyword2",
	"get_included_files": "keyword2",
	"get_loaded_extensions": "keyword2",
	"get_magic_quotes_gpc": "keyword2",
	"get_magic_quotes_runtime": "keyword2",
	"get_meta_tags": "keyword2",
	"get_object_vars": "keyword2",
	"get_parent_class": "keyword2",
	"get_required_files": "keyword2",
	"get_resource_type": "keyword2",
	"getallheaders": "keyword2",
	"getascent": "keyword2",
	"getcwd": "keyword2",
	"getdate": "keyword2",
	"getdescent": "keyword2",
	"getenv": "keyword2",
	"getheight": "keyword2",
	"gethostbyaddr": "keyword2",
	"gethostbyname": "keyword2",
	"gethostbynamel": "keyword2",
	"getimagesize": "keyword2",
	"getlastmod": "keyword2",
	"getleading": "keyword2",
	"getmxrr": "keyword2",
	"getmyinode": "keyword2",
	"getmypid": "keyword2",
	"getmyuid": "keyword2",
	"getopt": "keyword2",
	"getpeername": "keyword2",
	"getprotobyname": "keyword2",
	"getprotobynumber": "keyword2",
	"getrandmax": "keyword2",
	"getrusage": "keyword2",
	"getservbyname": "keyword2",
	"getservbyport": "keyword2",
	"getshape1": "keyword2",
	"getshape2": "keyword2",
	"getsockname": "keyword2",
	"getsockopt": "keyword2",
	"gettext": "keyword2",
	"gettimeofday": "keyword2",
	"gettype": "keyword2",
	"getwidth": "keyword2",
	"global": "keyword1",
	"gmdate": "keyword2",
	"gmmktime": "keyword2",
	"gmp_abs": "keyword2",
	"gmp_add": "keyword2",
	"gmp_and": "keyword2",
	"gmp_clrbit": "keyword2",
	"gmp_cmp": "keyword2",
	"gmp_com": "keyword2",
	"gmp_div": "keyword2",
	"gmp_div_q": "keyword2",
	"gmp_div_qr": "keyword2",
	"gmp_div_r": "keyword2",
	"gmp_divexact": "keyword2",
	"gmp_fact": "keyword2",
	"gmp_gcd": "keyword2",
	"gmp_gcdext": "keyword2",
	"gmp_hamdist": "keyword2",
	"gmp_init": "keyword2",
	"gmp_intval": "keyword2",
	"gmp_invert": "keyword2",
	"gmp_jacobi": "keyword2",
	"gmp_legendre": "keyword2",
	"gmp_mod": "keyword2",
	"gmp_mul": "keyword2",
	"gmp_neg": "keyword2",
	"gmp_or": "keyword2",
	"gmp_perfect_square": "keyword2",
	"gmp_popcount": "keyword2",
	"gmp_pow": "keyword2",
	"gmp_powm": "keyword2",
	"gmp_prob_prime": "keyword2",
	"gmp_random": "keyword2",
	"gmp_scan0": "keyword2",
	"gmp_scan1": "keyword2",
	"gmp_setbit": "keyword2",
	"gmp_sign": "keyword2",
	"gmp_sqrt": "keyword2",
	"gmp_sqrtrem": "keyword2",
	"gmp_strval": "keyword2",
	"gmp_sub": "keyword2",
	"gmp_xor": "keyword2",
	"gmstrftime": "keyword2",
	"gregoriantojd": "keyword2",
	"gzclose": "keyword2",
	"gzcompress": "keyword2",
	"gzdeflate": "keyword2",
	"gzencode": "keyword2",
	"gzeof": "keyword2",
	"gzfile": "keyword2",
	"gzgetc": "keyword2",
	"gzgets": "keyword2",
	"gzgetss": "keyword2",
	"gzinflate": "keyword2",
	"gzopen": "keyword2",
	"gzpassthru": "keyword2",
	"gzputs": "keyword2",
	"gzread": "keyword2",
	"gzrewind": "keyword2",
	"gzseek": "keyword2",
	"gztell": "keyword2",
	"gzuncompress": "keyword2",
	"gzwrite": "keyword2",
	"header": "keyword2",
	"headers_list": "keyword2",
	"headers_sent": "keyword2",
	"hebrev": "keyword2",
	"hebrevc": "keyword2",
	"hexdec": "keyword2",
	"highlight_file": "keyword2",
	"highlight_string": "keyword2",
	"htmlentities": "keyword2",
	"htmlspecialchars": "keyword2",
	"http_build_query": "keyword2",
	"hw_array2objrec": "keyword2",
	"hw_changeobject": "keyword2",
	"hw_children": "keyword2",
	"hw_childrenobj": "keyword2",
	"hw_close": "keyword2",
	"hw_connect": "keyword2",
	"hw_connection_info": "keyword2",
	"hw_cp": "keyword2",
	"hw_deleteobject": "keyword2",
	"hw_docbyanchor": "keyword2",
	"hw_docbyanchorobj": "keyword2",
	"hw_document_attributes": "keyword2",
	"hw_document_bodytag": "keyword2",
	"hw_document_content": "keyword2",
	"hw_document_setcontent": "keyword2",
	"hw_document_size": "keyword2",
	"hw_dummy": "keyword2",
	"hw_edittext": "keyword2",
	"hw_error": "keyword2",
	"hw_errormsg": "keyword2",
	"hw_free_document": "keyword2",
	"hw_getanchors": "keyword2",
	"hw_getanchorsobj": "keyword2",
	"hw_getandlock": "keyword2",
	"hw_getcgi": "keyword2",
	"hw_getchildcoll": "keyword2",
	"hw_getchildcollobj": "keyword2",
	"hw_getchilddoccoll": "keyword2",
	"hw_getchilddoccollobj": "keyword2",
	"hw_getobject": "keyword2",
	"hw_getobjectbyftquery": "keyword2",
	"hw_getobjectbyftquerycoll": "keyword2",
	"hw_getobjectbyftquerycollobj": "keyword2",
	"hw_getobjectbyftqueryobj": "keyword2",
	"hw_getobjectbyquery": "keyword2",
	"hw_getobjectbyquerycoll": "keyword2",
	"hw_getobjectbyquerycollobj": "keyword2",
	"hw_getobjectbyqueryobj": "keyword2",
	"hw_getparents": "keyword2",
	"hw_getparentsobj": "keyword2",
	"hw_getrellink": "keyword2",
	"hw_getremote": "keyword2",
	"hw_getremotechildren": "keyword2",
	"hw_getsrcbydestobj": "keyword2",
	"hw_gettext": "keyword2",
	"hw_getusername": "keyword2",
	"hw_identify": "keyword2",
	"hw_incollections": "keyword2",
	"hw_info": "keyword2",
	"hw_inscoll": "keyword2",
	"hw_insdoc": "keyword2",
	"hw_insertanchors": "keyword2",
	"hw_insertdocument": "keyword2",
	"hw_insertobject": "keyword2",
	"hw_mapid": "keyword2",
	"hw_modifyobject": "keyword2",
	"hw_mv": "keyword2",
	"hw_new_document": "keyword2",
	"hw_new_document_from_file": "keyword2",
	"hw_objrec2array": "keyword2",
	"hw_output_document": "keyword2",
	"hw_pconnect": "keyword2",
	"hw_pipecgi": "keyword2",
	"hw_pipedocument": "keyword2",
	"hw_root": "keyword2",
	"hw_setlinkroot": "keyword2",
	"hw_stat": "keyword2",
	"hw_unlock": "keyword2",
	"hw_who": "keyword2",
	"ibase_blob_add": "keyword2",
	"ibase_blob_cancel": "keyword2",
	"ibase_blob_close": "keyword2",
	"ibase_blob_create": "keyword2",
	"ibase_blob_echo": "keyword2",
	"ibase_blob_get": "keyword2",
	"ibase_blob_import": "keyword2",
	"ibase_blob_info": "keyword2",
	"ibase_blob_open": "keyword2",
	"ibase_close": "keyword2",
	"ibase_commit": "keyword2",
	"ibase_connect": "keyword2",
	"ibase_errmsg": "keyword2",
	"ibase_execute": "keyword2",
	"ibase_fetch_object": "keyword2",
	"ibase_fetch_row": "keyword2",
	"ibase_field_info": "keyword2",
	"ibase_free_query": "keyword2",
	"ibase_free_result": "keyword2",
	"ibase_num_fields": "keyword2",
	"ibase_pconnect": "keyword2",
	"ibase_prepare": "keyword2",
	"ibase_query": "keyword2",
	"ibase_rollback": "keyword2",
	"ibase_timefmt": "keyword2",
	"ibase_trans": "keyword2",
	"icap_create_calendar": "keyword2",
	"icap_delete_calendar": "keyword2",
	"icap_delete_event": "keyword2",
	"icap_fetch_event": "keyword2",
	"icap_list_alarms": "keyword2",
	"icap_list_events": "keyword2",
	"icap_open": "keyword2",
	"icap_popen": "keyword2",
	"icap_rename_calendar": "keyword2",
	"icap_reopen": "keyword2",
	"icap_snooze": "keyword2",
	"icap_store_event": "keyword2",
	"iconv": "keyword2",
	"iconv_get_encoding": "keyword2",
	"iconv_set_encoding": "keyword2",
	"idate": "keyword2",
	"if": "keyword1",
	"ifx_affected_rows": "keyword2",
	"ifx_blobinfile_mode": "keyword2",
	"ifx_byteasvarchar": "keyword2",
	"ifx_close": "keyword2",
	"ifx_connect": "keyword2",
	"ifx_copy_blob": "keyword2",
	"ifx_create_blob": "keyword2",
	"ifx_create_char": "keyword2",
	"ifx_do": "keyword2",
	"ifx_error": "keyword2",
	"ifx_errormsg": "keyword2",
	"ifx_fetch_row": "keyword2",
	"ifx_fieldproperties": "keyword2",
	"ifx_fieldtypes": "keyword2",
	"ifx_free_blob": "keyword2",
	"ifx_free_char": "keyword2",
	"ifx_free_result": "keyword2",
	"ifx_get_blob": "keyword2",
	"ifx_get_char": "keyword2",
	"ifx_getsqlca": "keyword2",
	"ifx_htmltbl_result": "keyword2",
	"ifx_nullformat": "keyword2",
	"ifx_num_fields": "keyword2",
	"ifx_num_rows": "keyword2",
	"ifx_pconnect": "keyword2",
	"ifx_prepare": "keyword2",
	"ifx_query": "keyword2",
	"ifx_textasvarchar": "keyword2",
	"ifx_update_blob": "keyword2",
	"ifx_update_char": "keyword2",
	"ifxus_close_slob": "keyword2",
	"ifxus_create_slob": "keyword2",
	"ifxus_free_slob": "keyword2",
	"ifxus_open_slob": "keyword2",
	"ifxus_read_slob": "keyword2",
	"ifxus_seek_slob": "keyword2",
	"ifxus_tell_slob": "keyword2",
	"ifxus_write_slob": "keyword2",
	"ignore_user_abort": "keyword2",
	"iis_addserver": "keyword2",
	"iis_getdirsecurity": "keyword2",
	"iis_getscriptmap": "keyword2",
	"iis_getserverbycomment": "keyword2",
	"iis_getserverbypath": "keyword2",
	"iis_getserverright": "keyword2",
	"iis_getservicestate": "keyword2",
	"iis_removeserver": "keyword2",
	"iis_setappsettings": "keyword2",
	"iis_setdirsecurity": "keyword2",
	"iis_setscriptmap": "keyword2",
	"iis_setserverright": "keyword2",
	"iis_startserver": "keyword2",
	"iis_startservice": "keyword2",
	"iis_stopserver": "keyword2",
	"iis_stopservice": "keyword2",
	"image2wbmp": "keyword2",
	"image_type_to_extension": "keyword2",
	"imagealphablending": "keyword2",
	"imagearc": "keyword2",
	"imagechar": "keyword2",
	"imagecharup": "keyword2",
	"imagecolorallocate": "keyword2",
	"imagecolorat": "keyword2",
	"imagecolorclosest": "keyword2",
	"imagecolorclosestalpha": "keyword2",
	"imagecolorclosesthwb": "keyword2",
	"imagecolordeallocate": "keyword2",
	"imagecolorexact": "keyword2",
	"imagecolorexactalpha": "keyword2",
	"imagecolormatch": "keyword2",
	"imagecolorresolve": "keyword2",
	"imagecolorresolvealpha": "keyword2",
	"imagecolorset": "keyword2",
	"imagecolorsforindex": "keyword2",
	"imagecolorstotal": "keyword2",
	"imagecolortransparent": "keyword2",
	"imagecopy": "keyword2",
	"imagecopymerge": "keyword2",
	"imagecopymergegray": "keyword2",
	"imagecopyresampled": "keyword2",
	"imagecopyresized": "keyword2",
	"imagecreate": "keyword2",
	"imagecreatefromgif": "keyword2",
	"imagecreatefromjpeg": "keyword2",
	"imagecreatefrompng": "keyword2",
	"imagecreatefromstring": "keyword2",
	"imagecreatefromwbmp": "keyword2",
	"imagecreatefromxbm": "keyword2",
	"imagecreatefromxpm": "keyword2",
	"imagecreatetruecolor": "keyword2",
	"imagedashedline": "keyword2",
	"imagedestroy": "keyword2",
	"imageellipse": "keyword2",
	"imagefill": "keyword2",
	"imagefilledarc": "keyword2",
	"imagefilledellipse": "keyword2",
	"imagefilledpolygon": "keyword2",
	"imagefilledrectangle": "keyword2",
	"imagefilltoborder": "keyword2",
	"imagefilter": "keyword2",
	"imagefontheight": "keyword2",
	"imagefontwidth": "keyword2",
	"imagegammacorrect": "keyword2",
	"imagegif": "keyword2",
	"imageinterlace": "keyword2",
	"imagejpeg": "keyword2",
	"imagelayereffect": "keyword2",
	"imageline": "keyword2",
	"imageloadfont": "keyword2",
	"imagepalettecopy": "keyword2",
	"imagepng": "keyword2",
	"imagepolygon": "keyword2",
	"imagepsbbox": "keyword2",
	"imagepscopyfont": "keyword2",
	"imagepsencodefont": "keyword2",
	"imagepsextendfont": "keyword2",
	"imagepsfreefont": "keyword2",
	"imagepsloadfont": "keyword2",
	"imagepsslantfont": "keyword2",
	"imagepstext": "keyword2",
	"imagerectangle": "keyword2",
	"imagerotate": "keyword2",
	"imagesetbrush": "keyword2",
	"imagesetpixel": "keyword2",
	"imagesetstyle": "keyword2",
	"imagesetthickness": "keyword2",
	"imagesettile": "keyword2",
	"imagestring": "keyword2",
	"imagestringup": "keyword2",
	"imagesx": "keyword2",
	"imagesy": "keyword2",
	"imagetruecolortopalette": "keyword2",
	"imagettfbbox": "keyword2",
	"imagettftext": "keyword2",
	"imagetypes": "keyword2",
	"imagewbmp": "keyword2",
	"imap_8bit": "keyword2",
	"imap_alerts": "keyword2",
	"imap_append": "keyword2",
	"imap_base64": "keyword2",
	"imap_binary": "keyword2",
	"imap_body": "keyword2",
	"imap_bodystruct": "keyword2",
	"imap_check": "keyword2",
	"imap_clearflag_full": "keyword2",
	"imap_close": "keyword2",
	"imap_create": "keyword2",
	"imap_createmailbox": "keyword2",
	"imap_delete": "keyword2",
	"imap_deletemailbox": "keyword2",
	"imap_errors": "keyword2",
	"imap_expunge": "keyword2",
	"imap_fetch_overview": "keyword2",
	"imap_fetchbody": "keyword2",
	"imap_fetchheader": "keyword2",
	"imap_fetchstructure": "keyword2",
	"imap_fetchtext": "keyword2",
	"imap_get_quota": "keyword2",
	"imap_getmailboxes": "keyword2",
	"imap_getsubscribed": "keyword2",
	"imap_header": "keyword2",
	"imap_headerinfo": "keyword2",
	"imap_headers": "keyword2",
	"imap_last_error": "keyword2",
	"imap_list": "keyword2",
	"imap_listmailbox": "keyword2",
	"imap_listsubscribed": "keyword2",
	"imap_lsub": "keyword2",
	"imap_mail": "keyword2",
	"imap_mail_compose": "keyword2",
	"imap_mail_copy": "keyword2",
	"imap_mail_move": "keyword2",
	"imap_mailboxmsginfo": "keyword2",
	"imap_mime_header_decode": "keyword2",
	"imap_msgno": "keyword2",
	"imap_num_msg": "keyword2",
	"imap_num_recent": "keyword2",
	"imap_open": "keyword2",
	"imap_ping": "keyword2",
	"imap_popen": "keyword2",
	"imap_qprint": "keyword2",
	"imap_rename": "keyword2",
	"imap_renamemailbox": "keyword2",
	"imap_reopen": "keyword2",
	"imap_rfc822_parse_adrlist": "keyword2",
	"imap_rfc822_parse_headers": "keyword2",
	"imap_rfc822_write_address": "keyword2",
	"imap_scan": "keyword2",
	"imap_scanmailbox": "keyword2",
	"imap_search": "keyword2",
	"imap_set_quota": "keyword2",
	"imap_setflag_full": "keyword2",
	"imap_sort": "keyword2",
	"imap_status": "keyword2",
	"imap_subscribe": "keyword2",
	"imap_uid": "keyword2",
	"imap_undelete": "keyword2",
	"imap_unsubscribe": "keyword2",
	"imap_utf7_decode": "keyword2",
	"imap_utf7_encode": "keyword2",
	"imap_utf8": "keyword2",
	"implements": "keyword1",
	"implode": "keyword2",
	"in_array": "keyword2",
	"include": "keyword1",
	"include_once": "keyword1",
	"ingres_autocommit": "keyword2",
	"ingres_close": "keyword2",
	"ingres_commit": "keyword2",
	"ingres_connect": "keyword2",
	"ingres_fetch_array": "keyword2",
	"ingres_fetch_object": "keyword2",
	"ingres_fetch_row": "keyword2",
	"ingres_field_length": "keyword2",
	"ingres_field_name": "keyword2",
	"ingres_field_nullable": "keyword2",
	"ingres_field_precision": "keyword2",
	"ingres_field_scale": "keyword2",
	"ingres_field_type": "keyword2",
	"ingres_num_fields": "keyword2",
	"ingres_num_rows": "keyword2",
	"ingres_pconnect": "keyword2",
	"ingres_query": "keyword2",
	"ingres_rollback": "keyword2",
	"ini_alter": "keyword2",
	"ini_get": "keyword2",
	"ini_restore": "keyword2",
	"ini_set": "keyword2",
	"instanceof": "operator",
	"interface": "keyword1",
	"intval": "keyword2",
	"ip2long": "keyword2",
	"iptcembed": "keyword2",
	"iptcparse": "keyword2",
	"ircg_channel_mode": "keyword2",
	"ircg_disconnect": "keyword2",
	"ircg_html_encode": "keyword2",
	"ircg_ignore_add": "keyword2",
	"ircg_ignore_del": "keyword2",
	"ircg_is_conn_alive": "keyword2",
	"ircg_join": "keyword2",
	"ircg_kick": "keyword2",
	"ircg_lookup_format_messages": "keyword2",
	"ircg_msg": "keyword2",
	"ircg_nick": "keyword2",
	"ircg_nickname_escape": "keyword2",
	"ircg_nickname_unescape": "keyword2",
	"ircg_notice": "keyword2",
	"ircg_part": "keyword2",
	"ircg_pconnect": "keyword2",
	"ircg_register_format_messages": "keyword2",
	"ircg_set_current": "keyword2",
	"ircg_topic": "keyword2",
	"ircg_whois": "keyword2",
	"is_array": "keyword2",
	"is_bool": "keyword2",
	"is_dir": "keyword2",
	"is_double": "keyword2",
	"is_executable": "keyword2",
	"is_file": "keyword2",
	"is_float": "keyword2",
	"is_int": "keyword2",
	"is_integer": "keyword2",
	"is_link": "keyword2",
	"is_long": "keyword2",
	"is_null": "keyword2",
	"is_numeric": "keyword2",
	"is_object": "keyword2",
	"is_readable": "keyword2",
	"is_real": "keyword2",
	"is_resource": "keyword2",
	"is_scalar": "keyword2",
	"is_string": "keyword2",
	"is_subclass_of": "keyword2",
	"is_uploaded_file": "keyword2",
	"is_writable": "keyword2",
	"is_writeable": "keyword2",
	"isset": "keyword1",
	"java_last_exception_clear": "keyword2",
	"java_last_exception_get": "keyword2",
	"jddayofweek": "keyword2",
	"jdmonthname": "keyword2",
	"jdtofrench": "keyword2",
	"jdtogregorian": "keyword2",
	"jdtojewish": "keyword2",
	"jdtojulian": "keyword2",
	"jdtounix": "keyword2",
	"jewishtojd": "keyword2",
	"join": "keyword2",
	"jpeg2wbmp": "keyword2",
	"juliantojd": "keyword2",
	"key": "keyword2",
	"krsort": "keyword2",
	"ksort": "keyword2",
	"labelframe": "keyword2",
	"lcg_value": "keyword2",
	"ldap_8859_to_t61": "keyword2",
	"ldap_add": "keyword2",
	"ldap_bind": "keyword2",
	"ldap_close": "keyword2",
	"ldap_compare": "keyword2",
	"ldap_connect": "keyword2",
	"ldap_count_entries": "keyword2",
	"ldap_delete": "keyword2",
	"ldap_dn2ufn": "keyword2",
	"ldap_err2str": "keyword2",
	"ldap_errno": "keyword2",
	"ldap_error": "keyword2",
	"ldap_explode_dn": "keyword2",
	"ldap_first_attribute": "keyword2",
	"ldap_first_entry": "keyword2",
	"ldap_first_reference": "keyword2",
	"ldap_free_result": "keyword2",
	"ldap_get_attributes": "keyword2",
	"ldap_get_dn": "keyword2",
	"ldap_get_entries": "keyword2",
	"ldap_get_option": "keyword2",
	"ldap_get_values": "keyword2",
	"ldap_get_values_len": "keyword2",
	"ldap_list": "keyword2",
	"ldap_mod_add": "keyword2",
	"ldap_mod_del": "keyword2",
	"ldap_mod_replace": "keyword2",
	"ldap_modify": "keyword2",
	"ldap_next_attribute": "keyword2",
	"ldap_next_entry": "keyword2",
	"ldap_next_reference": "keyword2",
	"ldap_parse_reference": "keyword2",
	"ldap_parse_result": "keyword2",
	"ldap_read": "keyword2",
	"ldap_rename": "keyword2",
	"ldap_search": "keyword2",
	"ldap_set_option": "keyword2",
	"ldap_t61_to_8859": "keyword2",
	"ldap_unbind": "keyword2",
	"leak": "keyword2",
	"levenshtein": "keyword2",
	"link": "keyword2",
	"linkinfo": "keyword2",
	"list": "keyword1",
	"listen": "keyword2",
	"localeconv": "keyword2",
	"localtime": "keyword2",
	"log": "keyword2",
	"log10": "keyword2",
	"long2ip": "keyword2",
	"lstat": "keyword2",
	"ltrim": "keyword2",
	"magic_quotes_runtime": "keyword2",
	"mail": "keyword2",
	"max": "keyword2",
	"mb_convert_case": "keyword2",
	"mb_strtolower": "keyword2",
	"mb_strtoupper": "keyword2",
	"mcal_append_event": "keyword2",
	"mcal_close": "keyword2",
	"mcal_create_calendar": "keyword2",
	"mcal_date_compare": "keyword2",
	"mcal_date_valid": "keyword2",
	"mcal_day_of_week": "keyword2",
	"mcal_day_of_year": "keyword2",
	"mcal_days_in_month": "keyword2",
	"mcal_delete_calendar": "keyword2",
	"mcal_delete_event": "keyword2",
	"mcal_event_add_attribute": "keyword2",
	"mcal_event_init": "keyword2",
	"mcal_event_set_alarm": "keyword2",
	"mcal_event_set_category": "keyword2",
	"mcal_event_set_class": "keyword2",
	"mcal_event_set_description": "keyword2",
	"mcal_event_set_end": "keyword2",
	"mcal_event_set_recur_daily": "keyword2",
	"mcal_event_set_recur_monthly_mday": "keyword2",
	"mcal_event_set_recur_monthly_wday": "keyword2",
	"mcal_event_set_recur_none": "keyword2",
	"mcal_event_set_recur_weekly": "keyword2",
	"mcal_event_set_recur_yearly": "keyword2",
	"mcal_event_set_start": "keyword2",
	"mcal_event_set_title": "keyword2",
	"mcal_fetch_current_stream_event": "keyword2",
	"mcal_fetch_event": "keyword2",
	"mcal_is_leap_year": "keyword2",
	"mcal_list_alarms": "keyword2",
	"mcal_list_events": "keyword2",
	"mcal_next_recurrence": "keyword2",
	"mcal_open": "keyword2",
	"mcal_popen": "keyword2",
	"mcal_rename_calendar": "keyword2",
	"mcal_reopen": "keyword2",
	"mcal_snooze": "keyword2",
	"mcal_store_event": "keyword2",
	"mcal_time_valid": "keyword2",
	"mcal_week_of_year": "keyword2",
	"mcrypt_cbc": "keyword2",
	"mcrypt_cfb": "keyword2",
	"mcrypt_create_iv": "keyword2",
	"mcrypt_decrypt": "keyword2",
	"mcrypt_ecb": "keyword2",
	"mcrypt_enc_get_algorithms_name": "keyword2",
	"mcrypt_enc_get_block_size": "keyword2",
	"mcrypt_enc_get_iv_size": "keyword2",
	"mcrypt_enc_get_key_size": "keyword2",
	"mcrypt_enc_get_modes_name": "keyword2",
	"mcrypt_enc_get_supported_key_sizes": "keyword2",
	"mcrypt_enc_is_block_algorithm": "keyword2",
	"mcrypt_enc_is_block_algorithm_mode": "keyword2",
	"mcrypt_enc_is_block_mode": "keyword2",
	"mcrypt_enc_self_test": "keyword2",
	"mcrypt_encrypt": "keyword2",
	"mcrypt_generic": "keyword2",
	"mcrypt_generic_deinit": "keyword2",
	"mcrypt_generic_end": "keyword2",
	"mcrypt_generic_init": "keyword2",
	"mcrypt_get_block_size": "keyword2",
	"mcrypt_get_cipher_name": "keyword2",
	"mcrypt_get_iv_size": "keyword2",
	"mcrypt_get_key_size": "keyword2",
	"mcrypt_list_algorithms": "keyword2",
	"mcrypt_list_modes": "keyword2",
	"mcrypt_module_close": "keyword2",
	"mcrypt_module_get_algo_block_size": "keyword2",
	"mcrypt_module_get_algo_key_size": "keyword2",
	"mcrypt_module_get_supported_key_sizes": "keyword2",
	"mcrypt_module_is_block_algorithm": "keyword2",
	"mcrypt_module_is_block_algorithm_mode": "keyword2",
	"mcrypt_module_is_block_mode": "keyword2",
	"mcrypt_module_open": "keyword2",
	"mcrypt_module_self_test": "keyword2",
	"mcrypt_ofb": "keyword2",
	"md5": "keyword2",
	"md5_file": "keyword2",
	"mdecrypt_generic": "keyword2",
	"metaphone": "keyword2",
	"method_exists": "keyword2",
	"mhash": "keyword2",
	"mhash_count": "keyword2",
	"mhash_get_block_size": "keyword2",
	"mhash_get_hash_name": "keyword2",
	"mhash_keygen_s2k": "keyword2",
	"microtime": "keyword2",
	"min": "keyword2",
	"ming_setcubicthreshold": "keyword2",
	"ming_setscale": "keyword2",
	"mkdir": "keyword2",
	"mktime": "keyword2",
	"move": "keyword2",
	"move_uploaded_file": "keyword2",
	"movepen": "keyword2",
	"movepento": "keyword2",
	"moveto": "keyword2",
	"msql": "keyword2",
	"msql_affected_rows": "keyword2",
	"msql_close": "keyword2",
	"msql_connect": "keyword2",
	"msql_create_db": "keyword2",
	"msql_createdb": "keyword2",
	"msql_data_seek": "keyword2",
	"msql_db_query": "keyword2",
	"msql_dbname": "keyword2",
	"msql_drop_db": "keyword2",
	"msql_dropdb": "keyword2",
	"msql_error": "keyword2",
	"msql_fetch_array": "keyword2",
	"msql_fetch_field": "keyword2",
	"msql_fetch_object": "keyword2",
	"msql_fetch_row": "keyword2",
	"msql_field_flags": "keyword2",
	"msql_field_len": "keyword2",
	"msql_field_name": "keyword2",
	"msql_field_seek": "keyword2",
	"msql_field_table": "keyword2",
	"msql_field_type": "keyword2",
	"msql_fieldflags": "keyword2",
	"msql_fieldlen": "keyword2",
	"msql_fieldname": "keyword2",
	"msql_fieldtable": "keyword2",
	"msql_fieldtype": "keyword2",
	"msql_free_result": "keyword2",
	"msql_freeresult": "keyword2",
	"msql_list_dbs": "keyword2",
	"msql_list_fields": "keyword2",
	"msql_list_tables": "keyword2",
	"msql_listdbs": "keyword2",
	"msql_listfields": "keyword2",
	"msql_listtables": "keyword2",
	"msql_num_fields": "keyword2",
	"msql_num_rows": "keyword2",
	"msql_numfields": "keyword2",
	"msql_numrows": "keyword2",
	"msql_pconnect": "keyword2",
	"msql_query": "keyword2",
	"msql_regcase": "keyword2",
	"msql_result": "keyword2",
	"msql_select_db": "keyword2",
	"msql_selectdb": "keyword2",
	"msql_tablename": "keyword2",
	"mssql_affected_rows": "keyword2",
	"mssql_close": "keyword2",
	"mssql_connect": "keyword2",
	"mssql_data_seek": "keyword2",
	"mssql_fetch_array": "keyword2",
	"mssql_fetch_batch": "keyword2",
	"mssql_fetch_field": "keyword2",
	"mssql_fetch_object": "keyword2",
	"mssql_fetch_row": "keyword2",
	"mssql_field_length": "keyword2",
	"mssql_field_name": "keyword2",
	"mssql_field_seek": "keyword2",
	"mssql_field_type": "keyword2",
	"mssql_free_result": "keyword2",
	"mssql_get_last_message": "keyword2",
	"mssql_min_client_severity": "keyword2",
	"mssql_min_error_severity": "keyword2",
	"mssql_min_message_severity": "keyword2",
	"mssql_min_server_severity": "keyword2",
	"mssql_next_result": "keyword2",
	"mssql_num_fields": "keyword2",
	"mssql_num_rows": "keyword2",
	"mssql_pconnect": "keyword2",
	"mssql_query": "keyword2",
	"mssql_result": "keyword2",
	"mssql_rows_affected": "keyword2",
	"mssql_select_db": "keyword2",
	"mt_getrandmax": "keyword2",
	"mt_rand": "keyword2",
	"mt_srand": "keyword2",
	"multcolor": "keyword2",
	"muscat_close": "keyword2",
	"muscat_get": "keyword2",
	"muscat_give": "keyword2",
	"muscat_setup": "keyword2",
	"muscat_setup_net": "keyword2",
	"mysql": "keyword2",
	"mysql_affected_rows": "keyword2",
	"mysql_close": "keyword2",
	"mysql_connect": "keyword2",
	"mysql_create_db": "keyword2",
	"mysql_createdb": "keyword2",
	"mysql_data_seek": "keyword2",
	"mysql_db_name": "keyword2",
	"mysql_db_query": "keyword2",
	"mysql_dbname": "keyword2",
	"mysql_drop_db": "keyword2",
	"mysql_dropdb": "keyword2",
	"mysql_errno": "keyword2",
	"mysql_error": "keyword2",
	"mysql_escape_string": "keyword2",
	"mysql_fetch_array": "keyword2",
	"mysql_fetch_assoc": "keyword2",
	"mysql_fetch_field": "keyword2",
	"mysql_fetch_lengths": "keyword2",
	"mysql_fetch_object": "keyword2",
	"mysql_fetch_row": "keyword2",
	"mysql_field_flags": "keyword2",
	"mysql_field_len": "keyword2",
	"mysql_field_name": "keyword2",
	"mysql_field_seek": "keyword2",
	"mysql_field_table": "keyword2",
	"mysql_field_type": "keyword2",
	"mysql_fieldflags": "keyword2",
	"mysql_fieldlen": "keyword2",
	"mysql_fieldname": "keyword2",
	"mysql_fieldtable": "keyword2",
	"mysql_fieldtype": "keyword2",
	"mysql_free_result": "keyword2",
	"mysql_freeresult": "keyword2",
	"mysql_get_client_info": "keyword2",
	"mysql_get_host_info": "keyword2",
	"mysql_get_proto_info": "keyword2",
	"mysql_get_server_info": "keyword2",
	"mysql_insert_id": "keyword2",
	"mysql_list_dbs": "keyword2",
	"mysql_list_fields": "keyword2",
	"mysql_list_tables": "keyword2",
	"mysql_listdbs": "keyword2",
	"mysql_listfields": "keyword2",
	"mysql_listtables": "keyword2",
	"mysql_num_fields": "keyword2",
	"mysql_num_rows": "keyword2",
	"mysql_numfields": "keyword2",
	"mysql_numrows": "keyword2",
	"mysql_pconnect": "keyword2",
	"mysql_query": "keyword2",
	"mysql_result": "keyword2",
	"mysql_select_db": "keyword2",
	"mysql_selectdb": "keyword2",
	"mysql_tablename": "keyword2",
	"mysql_unbuffered_query": "keyword2",
	"natcasesort": "keyword2",
	"natsort": "keyword2",
	"new": "keyword1",
	"new_xmldoc": "keyword2",
	"next": "keyword2",
	"nextframe": "keyword2",
	"nl2br": "keyword2",
	"notes_body": "keyword2",
	"notes_copy_db": "keyword2",
	"notes_create_db": "keyword2",
	"notes_create_note": "keyword2",
	"notes_drop_db": "keyword2",
	"notes_find_note": "keyword2",
	"notes_header_info": "keyword2",
	"notes_list_msgs": "keyword2",
	"notes_mark_read": "keyword2",
	"notes_mark_unread": "keyword2",
	"notes_nav_create": "keyword2",
	"notes_search": "keyword2",
	"notes_unread": "keyword2",
	"notes_version": "keyword2",
	"null": "keyword3",
	"number_format": "keyword2",
	"ob_end_clean": "keyword2",
	"ob_end_flush": "keyword2",
	"ob_get_clean": "keyword2",
	"ob_get_contents": "keyword2",
	"ob_get_flush": "keyword2",
	"ob_get_length": "keyword2",
	"ob_gzhandler": "keyword2",
	"ob_iconv_handler": "keyword2",
	"ob_implicit_flush": "keyword2",
	"ob_list_handlers": "keyword2",
	"ob_start": "keyword2",
	"ocibindbyname": "keyword2",
	"ocicancel": "keyword2",
	"ocicollappend": "keyword2",
	"ocicollassign": "keyword2",
	"ocicollassignelem": "keyword2",
	"ocicolldateappendelem": "keyword2",
	"ocicolldateassignelem": "keyword2",
	"ocicolldategetelem": "keyword2",
	"ocicollgetelem": "keyword2",
	"ocicollmax": "keyword2",
	"ocicollsize": "keyword2",
	"ocicolltrim": "keyword2",
	"ocicolumnisnull": "keyword2",
	"ocicolumnname": "keyword2",
	"ocicolumnprecision": "keyword2",
	"ocicolumnscale": "keyword2",
	"ocicolumnsize": "keyword2",
	"ocicolumntype": "keyword2",
	"ocicolumntyperaw": "keyword2",
	"ocicommit": "keyword2",
	"ocidefinebyname": "keyword2",
	"ocierror": "keyword2",
	"ociexecute": "keyword2",
	"ocifetch": "keyword2",
	"ocifetchinto": "keyword2",
	"ocifetchstatement": "keyword2",
	"ocifreecoll": "keyword2",
	"ocifreecursor": "keyword2",
	"ocifreedesc": "keyword2",
	"ocifreestatement": "keyword2",
	"ociinternaldebug": "keyword2",
	"ociloadlob": "keyword2",
	"ocilogoff": "keyword2",
	"ocilogon": "keyword2",
	"ocinewcollection": "keyword2",
	"ocinewcursor": "keyword2",
	"ocinewdescriptor": "keyword2",
	"ocinlogon": "keyword2",
	"ocinumcols": "keyword2",
	"ociparse": "keyword2",
	"ociplogon": "keyword2",
	"ociresult": "keyword2",
	"ocirollback": "keyword2",
	"ocirowcount": "keyword2",
	"ocisavelob": "keyword2",
	"ocisavelobfile": "keyword2",
	"ociserverversion": "keyword2",
	"ocisetprefetch": "keyword2",
	"ocistatementtype": "keyword2",
	"ociwritelobtofile": "keyword2",
	"octdec": "keyword2",
	"odbc_autocommit": "keyword2",
	"odbc_binmode": "keyword2",
	"odbc_close": "keyword2",
	"odbc_close_all": "keyword2",
	"odbc_columnprivileges": "keyword2",
	"odbc_columns": "keyword2",
	"odbc_commit": "keyword2",
	"odbc_connect": "keyword2",
	"odbc_cursor": "keyword2",
	"odbc_data_source": "keyword2",
	"odbc_do": "keyword2",
	"odbc_error": "keyword2",
	"odbc_errormsg": "keyword2",
	"odbc_exec": "keyword2",
	"odbc_execute": "keyword2",
	"odbc_fetch_array": "keyword2",
	"odbc_fetch_into": "keyword2",
	"odbc_fetch_object": "keyword2",
	"odbc_fetch_row": "keyword2",
	"odbc_field_len": "keyword2",
	"odbc_field_name": "keyword2",
	"odbc_field_num": "keyword2",
	"odbc_field_precision": "keyword2",
	"odbc_field_scale": "keyword2",
	"odbc_field_type": "keyword2",
	"odbc_foreignkeys": "keyword2",
	"odbc_free_result": "keyword2",
	"odbc_gettypeinfo": "keyword2",
	"odbc_longreadlen": "keyword2",
	"odbc_next_result": "keyword2",
	"odbc_num_fields": "keyword2",
	"odbc_num_rows": "keyword2",
	"odbc_pconnect": "keyword2",
	"odbc_prepare": "keyword2",
	"odbc_primarykeys": "keyword2",
	"odbc_procedurecolumns": "keyword2",
	"odbc_procedures": "keyword2",
	"odbc_result": "keyword2",
	"odbc_result_all": "keyword2",
	"odbc_rollback": "keyword2",
	"odbc_setoption": "keyword2",
	"odbc_specialcolumns": "keyword2",
	"odbc_statistics": "keyword2",
	"odbc_tableprivileges": "keyword2",
	"odbc_tables": "keyword2",
	"old_function": "keyword1",
	"open_listen_sock": "keyword2",
	"opendir": "keyword2",
	"openlog": "keyword2",
	"openssl_error_string": "keyword2",
	"openssl_free_key": "keyword2",
	"openssl_get_privatekey": "keyword2",
	"openssl_get_publickey": "keyword2",
	"openssl_open": "keyword2",
	"openssl_pkcs7_decrypt": "keyword2",
	"openssl_pkcs7_encrypt": "keyword2",
	"openssl_pkcs7_sign": "keyword2",
	"openssl_pkcs7_verify": "keyword2",
	"openssl_seal": "keyword2",
	"openssl_sign": "keyword2",
	"openssl_verify": "keyword2",
	"openssl_x509_checkpurpose": "keyword2",
	"openssl_x509_free": "keyword2",
	"openssl_x509_parse": "keyword2",
	"openssl_x509_read": "keyword2",
	"or": "operator",
	"ora_bind": "keyword2",
	"ora_close": "keyword2",
	"ora_columnname": "keyword2",
	"ora_columnsize": "keyword2",
	"ora_columntype": "keyword2",
	"ora_commit": "keyword2",
	"ora_commitoff": "keyword2",
	"ora_commiton": "keyword2",
	"ora_do": "keyword2",
	"ora_error": "keyword2",
	"ora_errorcode": "keyword2",
	"ora_exec": "keyword2",
	"ora_fetch": "keyword2",
	"ora_fetch_into": "keyword2",
	"ora_getcolumn": "keyword2",
	"ora_logoff": "keyword2",
	"ora_logon": "keyword2",
	"ora_numcols": "keyword2",
	"ora_numrows": "keyword2",
	"ora_open": "keyword2",
	"ora_parse": "keyword2",
	"ora_plogon": "keyword2",
	"ora_rollback": "keyword2",
	"orbit_caught_exception": "keyword2",
	"orbit_exception_id": "keyword2",
	"orbit_exception_value": "keyword2",
	"orbit_get_repository_id": "keyword2",
	"orbit_load_idl": "keyword2",
	"ord": "keyword2",
	"output": "keyword2",
	"ovrimos_close": "keyword2",
	"ovrimos_close_all": "keyword2",
	"ovrimos_commit": "keyword2",
	"ovrimos_connect": "keyword2",
	"ovrimos_cursor": "keyword2",
	"ovrimos_exec": "keyword2",
	"ovrimos_execute": "keyword2",
	"ovrimos_fetch_into": "keyword2",
	"ovrimos_fetch_row": "keyword2",
	"ovrimos_field_len": "keyword2",
	"ovrimos_field_name": "keyword2",
	"ovrimos_field_num": "keyword2",
	"ovrimos_field_type": "keyword2",
	"ovrimos_free_result": "keyword2",
	"ovrimos_longreadlen": "keyword2",
	"ovrimos_num_fields": "keyword2",
	"ovrimos_num_rows": "keyword2",
	"ovrimos_prepare": "keyword2",
	"ovrimos_result": "keyword2",
	"ovrimos_result_all": "keyword2",
	"ovrimos_rollback": "keyword2",
	"pack": "keyword2",
	"parse_ini_file": "keyword2",
	"parse_str": "keyword2",
	"parse_url": "keyword2",
	"passthru": "keyword2",
	"pathinfo": "keyword2",
	"pclose": "keyword2",
	"pdf_add_annotation": "keyword2",
	"pdf_add_bookmark": "keyword2",
	"pdf_add_launchlink": "keyword2",
	"pdf_add_locallink": "keyword2",
	"pdf_add_note": "keyword2",
	"pdf_add_outline": "keyword2",
	"pdf_add_pdflink": "keyword2",
	"pdf_add_thumbnail": "keyword2",
	"pdf_add_weblink": "keyword2",
	"pdf_arc": "keyword2",
	"pdf_arcn": "keyword2",
	"pdf_attach_file": "keyword2",
	"pdf_begin_page": "keyword2",
	"pdf_begin_pattern": "keyword2",
	"pdf_begin_template": "keyword2",
	"pdf_circle": "keyword2",
	"pdf_clip": "keyword2",
	"pdf_close": "keyword2",
	"pdf_close_image": "keyword2",
	"pdf_close_pdi": "keyword2",
	"pdf_close_pdi_page": "keyword2",
	"pdf_closepath": "keyword2",
	"pdf_closepath_fill_stroke": "keyword2",
	"pdf_closepath_stroke": "keyword2",
	"pdf_concat": "keyword2",
	"pdf_continue_text": "keyword2",
	"pdf_curveto": "keyword2",
	"pdf_delete": "keyword2",
	"pdf_end_page": "keyword2",
	"pdf_end_pattern": "keyword2",
	"pdf_end_template": "keyword2",
	"pdf_endpath": "keyword2",
	"pdf_fill": "keyword2",
	"pdf_fill_stroke": "keyword2",
	"pdf_findfont": "keyword2",
	"pdf_get_buffer": "keyword2",
	"pdf_get_font": "keyword2",
	"pdf_get_fontname": "keyword2",
	"pdf_get_fontsize": "keyword2",
	"pdf_get_image_height": "keyword2",
	"pdf_get_image_width": "keyword2",
	"pdf_get_parameter": "keyword2",
	"pdf_get_pdi_parameter": "keyword2",
	"pdf_get_pdi_value": "keyword2",
	"pdf_get_value": "keyword2",
	"pdf_initgraphics": "keyword2",
	"pdf_lineto": "keyword2",
	"pdf_makespotcolor": "keyword2",
	"pdf_moveto": "keyword2",
	"pdf_new": "keyword2",
	"pdf_open": "keyword2",
	"pdf_open_ccitt": "keyword2",
	"pdf_open_file": "keyword2",
	"pdf_open_gif": "keyword2",
	"pdf_open_image": "keyword2",
	"pdf_open_image_file": "keyword2",
	"pdf_open_jpeg": "keyword2",
	"pdf_open_memory_image": "keyword2",
	"pdf_open_pdi": "keyword2",
	"pdf_open_pdi_page": "keyword2",
	"pdf_open_png": "keyword2",
	"pdf_open_tiff": "keyword2",
	"pdf_place_image": "keyword2",
	"pdf_place_pdi_page": "keyword2",
	"pdf_rect": "keyword2",
	"pdf_restore": "keyword2",
	"pdf_rotate": "keyword2",
	"pdf_save": "keyword2",
	"pdf_scale": "keyword2",
	"pdf_set_border_color": "keyword2",
	"pdf_set_border_dash": "keyword2",
	"pdf_set_border_style": "keyword2",
	"pdf_set_char_spacing": "keyword2",
	"pdf_set_duration": "keyword2",
	"pdf_set_font": "keyword2",
	"pdf_set_horiz_scaling": "keyword2",
	"pdf_set_info": "keyword2",
	"pdf_set_info_author": "keyword2",
	"pdf_set_info_creator": "keyword2",
	"pdf_set_info_keywords": "keyword2",
	"pdf_set_info_subject": "keyword2",
	"pdf_set_info_title": "keyword2",
	"pdf_set_leading": "keyword2",
	"pdf_set_parameter": "keyword2",
	"pdf_set_text_pos": "keyword2",
	"pdf_set_text_rendering": "keyword2",
	"pdf_set_text_rise": "keyword2",
	"pdf_set_transition": "keyword2",
	"pdf_set_value": "keyword2",
	"pdf_set_word_spacing": "keyword2",
	"pdf_setcolor": "keyword2",
	"pdf_setdash": "keyword2",
	"pdf_setflat": "keyword2",
	"pdf_setfont": "keyword2",
	"pdf_setgray": "keyword2",
	"pdf_setgray_fill": "keyword2",
	"pdf_setgray_stroke": "keyword2",
	"pdf_setlinecap": "keyword2",
	"pdf_setlinejoin": "keyword2",
	"pdf_setlinewidth": "keyword2",
	"pdf_setmatrix": "keyword2",
	"pdf_setmiterlimit": "keyword2",
	"pdf_setpolydash": "keyword2",
	"pdf_setrgbcolor": "keyword2",
	"pdf_setrgbcolor_fill": "keyword2",
	"pdf_setrgbcolor_stroke": "keyword2",
	"pdf_show": "keyword2",
	"pdf_show_boxed": "keyword2",
	"pdf_show_xy": "keyword2",
	"pdf_skew": "keyword2",
	"pdf_stringwidth": "keyword2",
	"pdf_stroke": "keyword2",
	"pdf_translate": "keyword2",
	"pfpro_cleanup": "keyword2",
	"pfpro_init": "keyword2",
	"pfpro_process": "keyword2",
	"pfpro_process_raw": "keyword2",
	"pfpro_version": "keyword2",
	"pfsockopen": "keyword2",
	"pg_client_encoding": "keyword2",
	"pg_clientencoding": "keyword2",
	"pg_close": "keyword2",
	"pg_cmdtuples": "keyword2",
	"pg_connect": "keyword2",
	"pg_convert": "keyword2",
	"pg_dbname": "keyword2",
	"pg_delete": "keyword2",
	"pg_end_copy": "keyword2",
	"pg_errormessage": "keyword2",
	"pg_exec": "keyword2",
	"pg_fetch_all": "keyword2",
	"pg_fetch_array": "keyword2",
	"pg_fetch_assoc": "keyword2",
	"pg_fetch_object": "keyword2",
	"pg_fetch_row": "keyword2",
	"pg_fieldisnull": "keyword2",
	"pg_fieldname": "keyword2",
	"pg_fieldnum": "keyword2",
	"pg_fieldprtlen": "keyword2",
	"pg_fieldsize": "keyword2",
	"pg_fieldtype": "keyword2",
	"pg_freeresult": "keyword2",
	"pg_get_notify": "keyword2",
	"pg_get_pid": "keyword2",
	"pg_getlastoid": "keyword2",
	"pg_host": "keyword2",
	"pg_insert": "keyword2",
	"pg_loclose": "keyword2",
	"pg_locreate": "keyword2",
	"pg_loexport": "keyword2",
	"pg_loimport": "keyword2",
	"pg_loopen": "keyword2",
	"pg_loread": "keyword2",
	"pg_loreadall": "keyword2",
	"pg_lounlink": "keyword2",
	"pg_lowrite": "keyword2",
	"pg_meta_data": "keyword2",
	"pg_numfields": "keyword2",
	"pg_numrows": "keyword2",
	"pg_options": "keyword2",
	"pg_pconnect": "keyword2",
	"pg_ping": "keyword2",
	"pg_port": "keyword2",
	"pg_put_line": "keyword2",
	"pg_result": "keyword2",
	"pg_result_seek": "keyword2",
	"pg_select": "keyword2",
	"pg_set_client_encoding": "keyword2",
	"pg_setclientencoding": "keyword2",
	"pg_trace": "keyword2",
	"pg_tty": "keyword2",
	"pg_unescape_bytea": "keyword2",
	"pg_untrace": "keyword2",
	"pg_update": "keyword2",
	"php_logo_guid": "keyword2",
	"php_sapi_name": "keyword2",
	"php_uname": "keyword2",
	"phpcredits": "keyword2",
	"phpinfo": "keyword2",
	"phpversion": "keyword2",
	"pi": "keyword2",
	"png2wbmp": "keyword2",
	"popen": "keyword2",
	"pos": "keyword2",
	"posix_ctermid": "keyword2",
	"posix_getcwd": "keyword2",
	"posix_getegid": "keyword2",
	"posix_geteuid": "keyword2",
	"posix_getgid": "keyword2",
	"posix_getgrgid": "keyword2",
	"posix_getgrnam": "keyword2",
	"posix_getgroups": "keyword2",
	"posix_getlogin": "keyword2",
	"posix_getpgid": "keyword2",
	"posix_getpgrp": "keyword2",
	"posix_getpid": "keyword2",
	"posix_getppid": "keyword2",
	"posix_getpwnam": "keyword2",
	"posix_getpwuid": "keyword2",
	"posix_getrlimit": "keyword2",
	"posix_getsid": "keyword2",
	"posix_getuid": "keyword2",
	"posix_isatty": "keyword2",
	"posix_kill": "keyword2",
	"posix_mkfifo": "keyword2",
	"posix_setegid": "keyword2",
	"posix_seteuid": "keyword2",
	"posix_setgid": "keyword2",
	"posix_setpgid": "keyword2",
	"posix_setsid": "keyword2",
	"posix_setuid": "keyword2",
	"posix_times": "keyword2",
	"posix_ttyname": "keyword2",
	"posix_uname": "keyword2",
	"pow": "keyword2",
	"preg_grep": "keyword2",
	"preg_match": "keyword2",
	"preg_match_all": "keyword2",
	"preg_quote": "keyword2",
	"preg_replace": "keyword2",
	"preg_replace_callback": "keyword2",
	"preg_split": "keyword2",
	"prev": "keyword2",
	"print_r": "keyword2",
	"printer_abort": "keyword2",
	"printer_close": "keyword2",
	"printer_create_brush": "keyword2",
	"printer_create_dc": "keyword2",
	"printer_create_font": "keyword2",
	"printer_create_pen": "keyword2",
	"printer_delete_brush": "keyword2",
	"printer_delete_dc": "keyword2",
	"printer_delete_font": "keyword2",
	"printer_delete_pen": "keyword2",
	"printer_draw_bmp": "keyword2",
	"printer_draw_chord": "keyword2",
	"printer_draw_elipse": "keyword2",
	"printer_draw_line": "keyword2",
	"printer_draw_pie": "keyword2",
	"printer_draw_rectangle": "keyword2",
	"printer_draw_roundrect": "keyword2",
	"printer_draw_text": "keyword2",
	"printer_end_doc": "keyword2",
	"printer_end_page": "keyword2",
	"printer_get_option": "keyword2",
	"printer_list": "keyword2",
	"printer_logical_fontheight": "keyword2",
	"printer_open": "keyword2",
	"printer_select_brush": "keyword2",
	"printer_select_font": "keyword2",
	"printer_select_pen": "keyword2",
	"printer_set_option": "keyword2",
	"printer_start_doc": "keyword2",
	"printer_start_page": "keyword2",
	"printer_write": "keyword2",
	"printf": "keyword2",
	"private": "keyword1",
	"protected": "keyword1",
	"pspell_add_to_personal": "keyword2",
	"pspell_add_to_session": "keyword2",
	"pspell_check": "keyword2",
	"pspell_clear_session": "keyword2",
	"pspell_config_create": "keyword2",
	"pspell_config_ignore": "keyword2",
	"pspell_config_mode": "keyword2",
	"pspell_config_personal": "keyword2",
	"pspell_config_repl": "keyword2",
	"pspell_config_runtogether": "keyword2",
	"pspell_config_save_repl": "keyword2",
	"pspell_new": "keyword2",
	"pspell_new_config": "keyword2",
	"pspell_new_personal": "keyword2",
	"pspell_save_wordlist": "keyword2",
	"pspell_store_replacement": "keyword2",
	"pspell_suggest": "keyword2",
	"public": "keyword1",
	"putenv": "keyword2",
	"qdom_error": "keyword2",
	"qdom_tree": "keyword2",
	"quoted_printable_decode": "keyword2",
	"quotemeta": "keyword2",
	"rad2deg": "keyword2",
	"rand": "keyword2",
	"range": "keyword2",
	"rawurldecode": "keyword2",
	"rawurlencode": "keyword2",
	"read": "keyword2",
	"read_exif_data": "keyword2",
	"readdir": "keyword2",
	"readfile": "keyword2",
	"readgzfile": "keyword2",
	"readline": "keyword2",
	"readline_add_history": "keyword2",
	"readline_clear_history": "keyword2",
	"readline_completion_function": "keyword2",
	"readline_info": "keyword2",
	"readline_list_history": "keyword2",
	"readline_read_history": "keyword2",
	"readline_write_history": "keyword2",
	"readlink": "keyword2",
	"readv": "keyword2",
	"realpath": "keyword2",
	"recode": "keyword2",
	"recode_file": "keyword2",
	"recode_string": "keyword2",
	"recv": "keyword2",
	"recvfrom": "keyword2",
	"recvmsg": "keyword2",
	"register_shutdown_function": "keyword2",
	"register_tick_function": "keyword2",
	"remove": "keyword2",
	"rename": "keyword2",
	"require": "keyword1",
	"require_once": "keyword1",
	"reset": "keyword2",
	"restore_error_handler": "keyword2",
	"return": "keyword1",
	"rewind": "keyword2",
	"rewinddir": "keyword2",
	"rmdir": "keyword2",
	"rotate": "keyword2",
	"rotateto": "keyword2",
	"round": "keyword2",
	"rsort": "keyword2",
	"rtrim": "keyword2",
	"satellite_caught_exception": "keyword2",
	"satellite_exception_id": "keyword2",
	"satellite_exception_value": "keyword2",
	"satellite_get_repository_id": "keyword2",
	"satellite_load_idl": "keyword2",
	"save": "keyword2",
	"savetofile": "keyword2",
	"scale": "keyword2",
	"scaleto": "keyword2",
	"scandir": "keyword2",
	"select": "keyword2",
	"sem_acquire": "keyword2",
	"sem_get": "keyword2",
	"sem_release": "keyword2",
	"send": "keyword2",
	"sendmsg": "keyword2",
	"sendto": "keyword2",
	"serialize": "keyword2",
	"session_cache_limiter": "keyword2",
	"session_decode": "keyword2",
	"session_destroy": "keyword2",
	"session_encode": "keyword2",
	"session_get_cookie_params": "keyword2",
	"session_id": "keyword2",
	"session_is_registered": "keyword2",
	"session_module_name": "keyword2",
	"session_name": "keyword2",
	"session_register": "keyword2",
	"session_save_path": "keyword2",
	"session_set_cookie_params": "keyword2",
	"session_set_save_handler": "keyword2",
	"session_start": "keyword2",
	"session_unregister": "keyword2",
	"session_unset": "keyword2",
	"session_write_close": "keyword2",
	"set_content": "keyword2",
	"set_error_handler": "keyword2",
	"set_file_buffer": "keyword2",
	"set_iovec": "keyword2",
	"set_magic_quotes_runtime": "keyword2",
	"set_nonblock": "keyword2",
	"set_socket_blocking": "keyword2",
	"set_time_limit": "keyword2",
	"setaction": "keyword2",
	"setbackground": "keyword2",
	"setbounds": "keyword2",
	"setcolor": "keyword2",
	"setcookie": "keyword2",
	"setdepth": "keyword2",
	"setdimension": "keyword2",
	"setdown": "keyword2",
	"setfont": "keyword2",
	"setframes": "keyword2",
	"setheight": "keyword2",
	"sethit": "keyword2",
	"setindentation": "keyword2",
	"setleftfill": "keyword2",
	"setleftmargin": "keyword2",
	"setline": "keyword2",
	"setlinespacing": "keyword2",
	"setlocale": "keyword2",
	"setmargins": "keyword2",
	"setmatrix": "keyword2",
	"setname": "keyword2",
	"setover": "keyword2",
	"setrate": "keyword2",
	"setratio": "keyword2",
	"setrightfill": "keyword2",
	"setrightmargin": "keyword2",
	"setsockopt": "keyword2",
	"setspacing": "keyword2",
	"settype": "keyword2",
	"setup": "keyword2",
	"sha1": "keyword2",
	"sha1_file": "keyword2",
	"shell_exec": "keyword2",
	"shm_attach": "keyword2",
	"shm_detach": "keyword2",
	"shm_get_var": "keyword2",
	"shm_put_var": "keyword2",
	"shm_remove": "keyword2",
	"shm_remove_var": "keyword2",
	"shmop_close": "keyword2",
	"shmop_delete": "keyword2",
	"shmop_open": "keyword2",
	"shmop_read": "keyword2",
	"shmop_size": "keyword2",
	"shmop_write": "keyword2",
	"show_source": "keyword2",
	"shuffle": "keyword2",
	"shutdown": "keyword2",
	"signal": "keyword2",
	"similar_text": "keyword2",
	"sin": "keyword2",
	"sizeof": "keyword2",
	"skewx": "keyword2",
	"skewxto": "keyword2",
	"skewy": "keyword2",
	"skewyto": "keyword2",
	"sleep": "keyword2",
	"snmp_get_quick_print": "keyword2",
	"snmp_set_quick_print": "keyword2",
	"snmpget": "keyword2",
	"snmprealwalk": "keyword2",
	"snmpset": "keyword2",
	"snmpwalk": "keyword2",
	"snmpwalkoid": "keyword2",
	"socket": "keyword2",
	"socket_get_status": "keyword2",
	"socket_set_blocking": "keyword2",
	"socket_set_timeout": "keyword2",
	"socketpair": "keyword2",
	"sort": "keyword2",
	"soundex": "keyword2",
	"split": "keyword2",
	"spliti": "keyword2",
	"sprintf": "keyword2",
	"sql_regcase": "keyword2",
	"sqrt": "keyword2",
	"srand": "keyword2",
	"sscanf": "keyword2",
	"stat": "keyword2",
	"static": "keyword1",
	"str_pad": "keyword2",
	"str_repeat": "keyword2",
	"str_replace": "keyword2",
	"str_rot13": "keyword2",
	"str_split": "keyword2",
	"str_word_count": "keyword2",
	"strcasecmp": "keyword2",
	"strchr": "keyword2",
	"strcmp": "keyword2",
	"strcoll": "keyword2",
	"strcspn": "keyword2",
	"stream_context_create": "keyword2",
	"stream_context_set_option": "keyword2",
	"stream_context_set_params": "keyword2",
	"stream_filter_append": "keyword2",
	"stream_filter_prepend": "keyword2",
	"stream_get_status": "keyword2",
	"stream_select": "keyword2",
	"stream_set_blocking": "keyword2",
	"stream_set_timeout": "keyword2",
	"streammp3": "keyword2",
	"strerror": "keyword2",
	"strftime": "keyword2",
	"strip_tags": "keyword2",
	"stripcslashes": "keyword2",
	"stripos": "keyword2",
	"stripslashes": "keyword2",
	"stristr": "keyword2",
	"strlen": "keyword2",
	"strnatcasecmp": "keyword2",
	"strnatcmp": "keyword2",
	"strncasecmp": "keyword2",
	"strncmp": "keyword2",
	"strpbrk": "keyword2",
	"strpos": "keyword2",
	"strrchr": "keyword2",
	"strrev": "keyword2",
	"strrpos": "keyword2",
	"strspn": "keyword2",
	"strstr": "keyword2",
	"strtok": "keyword2",
	"strtolower": "keyword2",
	"strtotime": "keyword2",
	"strtoupper": "keyword2",
	"strtr": "keyword2",
	"strval": "keyword2",
	"substr": "keyword2",
	"substr_compare": "keyword2",
	"substr_count": "keyword2",
	"substr_replace": "keyword2",
	"swf_actiongeturl": "keyword2",
	"swf_actiongotoframe": "keyword2",
	"swf_actiongotolabel": "keyword2",
	"swf_actionnextframe": "keyword2",
	"swf_actionplay": "keyword2",
	"swf_actionprevframe": "keyword2",
	"swf_actionsettarget": "keyword2",
	"swf_actionstop": "keyword2",
	"swf_actiontogglequality": "keyword2",
	"swf_actionwaitforframe": "keyword2",
	"swf_addbuttonrecord": "keyword2",
	"swf_addcolor": "keyword2",
	"swf_closefile": "keyword2",
	"swf_definebitmap": "keyword2",
	"swf_definefont": "keyword2",
	"swf_defineline": "keyword2",
	"swf_definepoly": "keyword2",
	"swf_definerect": "keyword2",
	"swf_definetext": "keyword2",
	"swf_endbutton": "keyword2",
	"swf_enddoaction": "keyword2",
	"swf_endshape": "keyword2",
	"swf_endsymbol": "keyword2",
	"swf_fontsize": "keyword2",
	"swf_fontslant": "keyword2",
	"swf_fonttracking": "keyword2",
	"swf_getbitmapinfo": "keyword2",
	"swf_getfontinfo": "keyword2",
	"swf_getframe": "keyword2",
	"swf_labelframe": "keyword2",
	"swf_lookat": "keyword2",
	"swf_modifyobject": "keyword2",
	"swf_mulcolor": "keyword2",
	"swf_nextid": "keyword2",
	"swf_oncondition": "keyword2",
	"swf_openfile": "keyword2",
	"swf_ortho": "keyword2",
	"swf_ortho2": "keyword2",
	"swf_perspective": "keyword2",
	"swf_placeobject": "keyword2",
	"swf_polarview": "keyword2",
	"swf_popmatrix": "keyword2",
	"swf_posround": "keyword2",
	"swf_pushmatrix": "keyword2",
	"swf_removeobject": "keyword2",
	"swf_rotate": "keyword2",
	"swf_scale": "keyword2",
	"swf_setfont": "keyword2",
	"swf_setframe": "keyword2",
	"swf_shapearc": "keyword2",
	"swf_shapecurveto": "keyword2",
	"swf_shapecurveto3": "keyword2",
	"swf_shapefillbitmapclip": "keyword2",
	"swf_shapefillbitmaptile": "keyword2",
	"swf_shapefilloff": "keyword2",
	"swf_shapefillsolid": "keyword2",
	"swf_shapelinesolid": "keyword2",
	"swf_shapelineto": "keyword2",
	"swf_shapemoveto": "keyword2",
	"swf_showframe": "keyword2",
	"swf_startbutton": "keyword2",
	"swf_startdoaction": "keyword2",
	"swf_startshape": "keyword2",
	"swf_startsymbol": "keyword2",
	"swf_textwidth": "keyword2",
	"swf_translate": "keyword2",
	"swf_viewport": "keyword2",
	"swfaction": "keyword2",
	"swfbitmap": "keyword2",
	"swfbutton": "keyword2",
	"swfbutton_keypress": "keyword2",
	"swffill": "keyword2",
	"swffont": "keyword2",
	"swfgradient": "keyword2",
	"swfmorph": "keyword2",
	"swfmovie": "keyword2",
	"swfshape": "keyword2",
	"swfsprite": "keyword2",
	"swftext": "keyword2",
	"swftextfield": "keyword2",
	"switch": "keyword1",
	"sybase_affected_rows": "keyword2",
	"sybase_close": "keyword2",
	"sybase_connect": "keyword2",
	"sybase_data_seek": "keyword2",
	"sybase_fetch_array": "keyword2",
	"sybase_fetch_field": "keyword2",
	"sybase_fetch_object": "keyword2",
	"sybase_fetch_row": "keyword2",
	"sybase_field_seek": "keyword2",
	"sybase_free_result": "keyword2",
	"sybase_get_last_message": "keyword2",
	"sybase_min_client_severity": "keyword2",
	"sybase_min_error_severity": "keyword2",
	"sybase_min_message_severity": "keyword2",
	"sybase_min_server_severity": "keyword2",
	"sybase_num_fields": "keyword2",
	"sybase_num_rows": "keyword2",
	"sybase_pconnect": "keyword2",
	"sybase_query": "keyword2",
	"sybase_result": "keyword2",
	"sybase_select_db": "keyword2",
	"symlink": "keyword2",
	"syslog": "keyword2",
	"system": "keyword2",
	"tan": "keyword2",
	"tempnam": "keyword2",
	"textdomain": "keyword2",
	"throw": "keyword1",
	"time": "keyword2",
	"time_nanosleep": "keyword2",
	"tmpfile": "keyword2",
	"touch": "keyword2",
	"trigger_error": "keyword2",
	"trim": "keyword2",
	"true": "keyword3",
	"try": "keyword1",
	"uasort": "keyword2",
	"ucfirst": "keyword2",
	"ucwords": "keyword2",
	"udm_add_search_limit": "keyword2",
	"udm_alloc_agent": "keyword2",
	"udm_api_version": "keyword2",
	"udm_clear_search_limits": "keyword2",
	"udm_errno": "keyword2",
	"udm_error": "keyword2",
	"udm_find": "keyword2",
	"udm_free_agent": "keyword2",
	"udm_free_ispell_data": "keyword2",
	"udm_free_res": "keyword2",
	"udm_get_doc_count": "keyword2",
	"udm_get_res_field": "keyword2",
	"udm_get_res_param": "keyword2",
	"udm_load_ispell_data": "keyword2",
	"udm_set_agent_param": "keyword2",
	"uksort": "keyword2",
	"umask": "keyword2",
	"uniqid": "keyword2",
	"unixtojd": "keyword2",
	"unlink": "keyword2",
	"unpack": "keyword2",
	"unregister_tick_function": "keyword2",
	"unserialize": "keyword2",
	"unset": "keyword2",
	"urldecode": "keyword2",
	"urlencode": "keyword2",
	"user_error": "keyword2",
	"usleep": "keyword2",
	"usort": "keyword2",
	"utf8_decode": "keyword2",
	"utf8_encode": "keyword2",
	"var": "keyword1",
	"var_dump": "keyword2",
	"velocis_autocommit": "keyword2",
	"velocis_close": "keyword2",
	"velocis_commit": "keyword2",
	"velocis_connect": "keyword2",
	"velocis_exec": "keyword2",
	"velocis_fetch": "keyword2",
	"velocis_fieldname": "keyword2",
	"velocis_fieldnum": "keyword2",
	"velocis_freeresult": "keyword2",
	"velocis_off_autocommit": "keyword2",
	"velocis_result": "keyword2",
	"velocis_rollback": "keyword2",
	"virtual": "keyword2",
	"vpopmail_add_alias_domain": "keyword2",
	"vpopmail_add_alias_domain_ex": "keyword2",
	"vpopmail_add_domain": "keyword2",
	"vpopmail_add_domain_ex": "keyword2",
	"vpopmail_add_user": "keyword2",
	"vpopmail_auth_user": "keyword2",
	"vpopmail_del_domain": "keyword2",
	"vpopmail_del_domain_ex": "keyword2",
	"vpopmail_del_user": "keyword2",
	"vpopmail_error": "keyword2",
	"vpopmail_passwd": "keyword2",
	"vpopmail_set_user_quota": "keyword2",
	"wddx_add_vars": "keyword2",
	"wddx_deserialize": "keyword2",
	"wddx_packet_end": "keyword2",
	"wddx_packet_start": "keyword2",
	"wddx_serialize_value": "keyword2",
	"wddx_serialize_vars": "keyword2",
	"while": "keyword1",
	"wordwrap": "keyword2",
	"write": "keyword2",
	"writev": "keyword2",
	"xml_error_string": "keyword2",
	"xml_get_current_byte_index": "keyword2",
	"xml_get_current_column_number": "keyword2",
	"xml_get_current_line_number": "keyword2",
	"xml_get_error_code": "keyword2",
	"xml_parse": "keyword2",
	"xml_parse_into_struct": "keyword2",
	"xml_parser_create": "keyword2",
	"xml_parser_create_ns": "keyword2",
	"xml_parser_free": "keyword2",
	"xml_parser_get_option": "keyword2",
	"xml_parser_set_option": "keyword2",
	"xml_set_character_data_handler": "keyword2",
	"xml_set_default_handler": "keyword2",
	"xml_set_element_handler": "keyword2",
	"xml_set_end_namespace_decl_handler": "keyword2",
	"xml_set_external_entity_ref_handler": "keyword2",
	"xml_set_notation_decl_handler": "keyword2",
	"xml_set_object": "keyword2",
	"xml_set_processing_instruction_handler": "keyword2",
	"xml_set_start_namespace_decl_handler": "keyword2",
	"xml_set_unparsed_entity_decl_handler": "keyword2",
	"xmldoc": "keyword2",
	"xmldocfile": "keyword2",
	"xmltree": "keyword2",
	"xpath_eval": "keyword2",
	"xpath_eval_expression": "keyword2",
	"xptr_eval": "keyword2",
	"xslt_closelog": "keyword2",
	"xslt_create": "keyword2",
	"xslt_errno": "keyword2",
	"xslt_error": "keyword2",
	"xslt_fetch_result": "keyword2",
	"xslt_free": "keyword2",
	"xslt_openlog": "keyword2",
	"xslt_output_begintransform": "keyword2",
	"xslt_output_endtransform": "keyword2",
	"xslt_process": "keyword2",
	"xslt_run": "keyword2",
	"xslt_set_base": "keyword2",
	"xslt_set_encoding": "keyword2",
	"xslt_set_error_handler": "keyword2",
	"xslt_set_sax_handler": "keyword2",
	"xslt_set_scheme_handler": "keyword2",
	"xslt_transform": "keyword2",
	"yaz_addinfo": "keyword2",
	"yaz_ccl_conf": "keyword2",
	"yaz_ccl_parse": "keyword2",
	"yaz_close": "keyword2",
	"yaz_connect": "keyword2",
	"yaz_database": "keyword2",
	"yaz_element": "keyword2",
	"yaz_errno": "keyword2",
	"yaz_error": "keyword2",
	"yaz_hits": "keyword2",
	"yaz_itemorder": "keyword2",
	"yaz_present": "keyword2",
	"yaz_range": "keyword2",
	"yaz_record": "keyword2",
	"yaz_scan": "keyword2",
	"yaz_scan_result": "keyword2",
	"yaz_search": "keyword2",
	"yaz_syntax": "keyword2",
	"yaz_wait": "keyword2",
	"yp_all": "keyword2",
	"yp_cat": "keyword2",
	"yp_err_string": "keyword2",
	"yp_errno": "keyword2",
	"yp_first": "keyword2",
	"yp_get_default_domain": "keyword2",
	"yp_master": "keyword2",
	"yp_match": "keyword2",
	"yp_next": "keyword2",
	"yp_order": "keyword2",
	"zend_logo_guid": "keyword2",
	"zend_test_func": "keyword2",
	"zend_version": "keyword2",
	"zzip_close": "keyword2",
	"zzip_closedir": "keyword2",
	"zzip_entry_compressedsize": "keyword2",
	"zzip_entry_compressionmethod": "keyword2",
	"zzip_entry_filesize": "keyword2",
	"zzip_entry_name": "keyword2",
	"zzip_open": "keyword2",
	"zzip_opendir": "keyword2",
	"zzip_read": "keyword2",
	"zzip_readdir": "keyword2",
}

# Keywords dict for php_php_literal ruleset.
php_php_literal_keywords_dict = {}

# Keywords dict for php_javascript ruleset.
php_javascript_keywords_dict = {}

# Keywords dict for php_javascript_php ruleset.
php_javascript_php_keywords_dict = {}

# Keywords dict for php_phpdoc ruleset.
php_phpdoc_keywords_dict = {
	"@abstract": "label",
	"@access": "label",
	"@author": "label",
	"@category": "label",
	"@copyright": "label",
	"@deprecated": "label",
	"@example": "label",
	"@filesource": "label",
	"@final": "label",
	"@global": "label",
	"@id": "label",
	"@ignore": "label",
	"@inheritdoc": "label",
	"@internal": "label",
	"@license": "label",
	"@link": "label",
	"@name": "label",
	"@package": "label",
	"@param": "label",
	"@return": "label",
	"@see": "label",
	"@since": "label",
	"@source": "label",
	"@static": "label",
	"@staticvar": "label",
	"@subpackage": "label",
	"@toc": "label",
	"@todo": "label",
	"@tutorial": "label",
	"@uses": "label",
	"@var": "label",
	"@version": "label",
}

# Dictionary of keywords dictionaries for php mode.
keywordsDictDict = {
	"php_javascript": php_javascript_keywords_dict,
	"php_javascript_php": php_javascript_php_keywords_dict,
	"php_main": php_main_keywords_dict,
	"php_php": php_php_keywords_dict,
	"php_php_literal": php_php_literal_keywords_dict,
	"php_phpdoc": php_phpdoc_keywords_dict,
	"php_tags": php_tags_keywords_dict,
	"php_tags_literal": php_tags_literal_keywords_dict,
}

# Rules for php_main ruleset.

def rule0(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?php", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule1(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule2(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<%=", end="%>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule3(colorer, s, i):
    return colorer.match_span(s, i, kind="comment1", begin="<!--", end="-->",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule4(colorer, s, i):
    return colorer.match_span_regexp(s, i, kind="markup", begin="<SCRIPT\\s+LANGUAGE=\"?PHP\"?>", end="</SCRIPT>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule5(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<SCRIPT", end="</SCRIPT>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="JAVASCRIPT",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule6(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<STYLE", end="</STYLE>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="html::CSS",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule7(colorer, s, i):
    return colorer.match_span(s, i, kind="keyword2", begin="<!", end=">",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="xml::DTD-TAGS",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule8(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<", end=">",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="TAGS",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule9(colorer, s, i):
    return colorer.match_span(s, i, kind="literal2", begin="&", end=";",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=True)

# Rules dict for main ruleset.
rulesDict1 = {
	"&": [rule9,],
	"<": [rule0,rule1,rule2,rule3,rule4,rule5,rule6,rule7,rule8,],
}

# Rules for php_tags ruleset.

def rule10(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?php", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule11(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule12(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<%=", end="%>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule13(colorer, s, i):
    return colorer.match_span(s, i, kind="literal1", begin="\"", end="\"",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="TAGS_LITERAL",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule14(colorer, s, i):
    return colorer.match_span(s, i, kind="literal1", begin="'", end="'",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="TAGS_LITERAL",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule15(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="=",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

# Rules dict for tags ruleset.
rulesDict2 = {
	"\"": [rule13,],
	"'": [rule14,],
	"<": [rule10,rule11,rule12,],
	"=": [rule15,],
}

# Rules for php_tags_literal ruleset.

def rule16(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?php", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule17(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule18(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<%=", end="%>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

# Rules dict for tags_literal ruleset.
rulesDict3 = {
	"<": [rule16,rule17,rule18,],
}

# Rules for php_php ruleset.

def rule19(colorer, s, i):
    return colorer.match_span(s, i, kind="comment3", begin="/**", end="*/",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHPDOC",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule20(colorer, s, i):
    return colorer.match_span(s, i, kind="comment1", begin="/*", end="*/",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule21(colorer, s, i):
    return colorer.match_span(s, i, kind="literal1", begin="\"", end="\"",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP_LITERAL",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule22(colorer, s, i):
    return colorer.match_span(s, i, kind="literal1", begin="'", end="'",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule23(colorer, s, i):
    return colorer.match_span(s, i, kind="literal1", begin="`", end="`",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP_LITERAL",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule24(colorer, s, i):
    return colorer.match_eol_span(s, i, kind="comment2", seq="//",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="", exclude_match=False)

def rule25(colorer, s, i):
    return colorer.match_eol_span(s, i, kind="comment1", seq="#",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="", exclude_match=False)

def rule26(colorer, s, i):
    return colorer.match_span_regexp(s, i, kind="literal1", begin="<<<[[:space:]'\"]*([[:alnum:]_]+)[[:space:]'\"]*", end="$1",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="PHP_LITERAL",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule27(colorer, s, i):
    return colorer.match_mark_following(s, i, kind="keyword3", pattern="$",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, exclude_match=False)

def rule28(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="=",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule29(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="->",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule30(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="!",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule31(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq=">=",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule32(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="<=",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule33(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="=",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule34(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="+",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule35(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="-",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule36(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="/",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule37(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="*",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule38(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq=">",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule39(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="<",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule40(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="%",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule41(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="&",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule42(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="|",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule43(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="^",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule44(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="~",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule45(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq=".",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule46(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="}",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule47(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="{",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule48(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq=",",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule49(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq=";",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule50(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="]",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule51(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="[",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule52(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="?",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule53(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq="@",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule54(colorer, s, i):
    return colorer.match_seq(s, i, kind="operator", seq=":",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule55(colorer, s, i):
    return colorer.match_mark_previous(s, i, kind="function", pattern="(",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, exclude_match=True)

def rule56(colorer, s, i):
    return colorer.match_keywords(s, i)

# Rules dict for php ruleset.
rulesDict4 = {
	"!": [rule30,],
	"\"": [rule21,],
	"#": [rule25,],
	"$": [rule27,],
	"%": [rule40,],
	"&": [rule41,],
	"'": [rule22,],
	"(": [rule55,],
	"*": [rule37,],
	"+": [rule34,],
	",": [rule48,],
	"-": [rule29,rule35,],
	".": [rule45,],
	"/": [rule19,rule20,rule24,rule36,],
	"0": [rule56,],
	"1": [rule56,],
	"2": [rule56,],
	"3": [rule56,],
	"4": [rule56,],
	"5": [rule56,],
	"6": [rule56,],
	"7": [rule56,],
	"8": [rule56,],
	"9": [rule56,],
	":": [rule54,],
	";": [rule49,],
	"<": [rule26,rule32,rule39,],
	"=": [rule28,rule33,],
	">": [rule31,rule38,],
	"?": [rule52,],
	"@": [rule53,rule56,],
	"A": [rule56,],
	"B": [rule56,],
	"C": [rule56,],
	"D": [rule56,],
	"E": [rule56,],
	"F": [rule56,],
	"G": [rule56,],
	"H": [rule56,],
	"I": [rule56,],
	"J": [rule56,],
	"K": [rule56,],
	"L": [rule56,],
	"M": [rule56,],
	"N": [rule56,],
	"O": [rule56,],
	"P": [rule56,],
	"Q": [rule56,],
	"R": [rule56,],
	"S": [rule56,],
	"T": [rule56,],
	"U": [rule56,],
	"V": [rule56,],
	"W": [rule56,],
	"X": [rule56,],
	"Y": [rule56,],
	"Z": [rule56,],
	"[": [rule51,],
	"]": [rule50,],
	"^": [rule43,],
	"_": [rule56,],
	"`": [rule23,],
	"a": [rule56,],
	"b": [rule56,],
	"c": [rule56,],
	"d": [rule56,],
	"e": [rule56,],
	"f": [rule56,],
	"g": [rule56,],
	"h": [rule56,],
	"i": [rule56,],
	"j": [rule56,],
	"k": [rule56,],
	"l": [rule56,],
	"m": [rule56,],
	"n": [rule56,],
	"o": [rule56,],
	"p": [rule56,],
	"q": [rule56,],
	"r": [rule56,],
	"s": [rule56,],
	"t": [rule56,],
	"u": [rule56,],
	"v": [rule56,],
	"w": [rule56,],
	"x": [rule56,],
	"y": [rule56,],
	"z": [rule56,],
	"{": [rule47,],
	"|": [rule42,],
	"}": [rule46,],
	"~": [rule44,],
}

# Rules for php_php_literal ruleset.

def rule57(colorer, s, i):
    return colorer.match_mark_following(s, i, kind="keyword3", pattern="$",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, exclude_match=False)

# Rules dict for php_literal ruleset.
rulesDict5 = {
	"$": [rule57,],
}

# Rules for php_javascript ruleset.

def rule58(colorer, s, i):
    return colorer.match_seq(s, i, kind="markup", seq=">",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="JAVASCRIPT+PHP")

def rule59(colorer, s, i):
    return colorer.match_seq(s, i, kind="markup", seq="SRC=",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="BACK_TO_HTML")

# Rules dict for javascript ruleset.
rulesDict6 = {
	">": [rule58,],
	"S": [rule59,],
}

# Rules for php_javascript_php ruleset.

def rule60(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?php", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="php::PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule61(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<?", end="?>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="php::PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule62(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<%=", end="%>",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="php::PHP",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)


# Rules dict for javascript_php ruleset.
rulesDict7 = {
	"<": [rule60,rule61,rule62,],
}

# Rules for php_phpdoc ruleset.

def rule63(colorer, s, i):
    return colorer.match_seq(s, i, kind="comment3", seq="{",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule64(colorer, s, i):
    return colorer.match_seq(s, i, kind="comment3", seq="*",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule65(colorer, s, i):
    return colorer.match_span(s, i, kind="comment2", begin="<!--", end="-->",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="",exclude_match=False,
        no_escape=False, no_line_break=False, no_word_break=False)

def rule66(colorer, s, i):
    return colorer.match_seq(s, i, kind="comment3", seq="<<",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule67(colorer, s, i):
    return colorer.match_seq(s, i, kind="comment3", seq="<=",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule68(colorer, s, i):
    return colorer.match_seq(s, i, kind="comment3", seq="< ",
        at_line_start=False, at_whitespace_end=False, at_word_start=False, delegate="")

def rule69(colorer, s, i):
    return colorer.match_span(s, i, kind="markup", begin="<", end=">",
        at_line_start=False, at_whitespace_end=False, at_word_start=False,
        delegate="xml::TAGS",exclude_match=False,
        no_escape=False, no_line_break=True, no_word_break=False)

def rule70(colorer, s, i):
    return colorer.match_keywords(s, i)

# Rules dict for phpdoc ruleset.
rulesDict8 = {
	"*": [rule64,],
	"0": [rule70,],
	"1": [rule70,],
	"2": [rule70,],
	"3": [rule70,],
	"4": [rule70,],
	"5": [rule70,],
	"6": [rule70,],
	"7": [rule70,],
	"8": [rule70,],
	"9": [rule70,],
	"<": [rule65,rule66,rule67,rule68,rule69,],
	"@": [rule70,],
	"A": [rule70,],
	"B": [rule70,],
	"C": [rule70,],
	"D": [rule70,],
	"E": [rule70,],
	"F": [rule70,],
	"G": [rule70,],
	"H": [rule70,],
	"I": [rule70,],
	"J": [rule70,],
	"K": [rule70,],
	"L": [rule70,],
	"M": [rule70,],
	"N": [rule70,],
	"O": [rule70,],
	"P": [rule70,],
	"Q": [rule70,],
	"R": [rule70,],
	"S": [rule70,],
	"T": [rule70,],
	"U": [rule70,],
	"V": [rule70,],
	"W": [rule70,],
	"X": [rule70,],
	"Y": [rule70,],
	"Z": [rule70,],
	"_": [rule70,],
	"a": [rule70,],
	"b": [rule70,],
	"c": [rule70,],
	"d": [rule70,],
	"e": [rule70,],
	"f": [rule70,],
	"g": [rule70,],
	"h": [rule70,],
	"i": [rule70,],
	"j": [rule70,],
	"k": [rule70,],
	"l": [rule70,],
	"m": [rule70,],
	"n": [rule70,],
	"o": [rule70,],
	"p": [rule70,],
	"q": [rule70,],
	"r": [rule70,],
	"s": [rule70,],
	"t": [rule70,],
	"u": [rule70,],
	"v": [rule70,],
	"w": [rule70,],
	"x": [rule70,],
	"y": [rule70,],
	"z": [rule70,],
	"{": [rule63,],
}

# x.rulesDictDict for php mode.
rulesDictDict = {
	"php_javascript": rulesDict6,
	"php_javascript_php": rulesDict7,
	"php_main": rulesDict1,
	"php_php": rulesDict4,
	"php_php_literal": rulesDict5,
	"php_phpdoc": rulesDict8,
	"php_tags": rulesDict2,
	"php_tags_literal": rulesDict3,
}

# Import dict for php mode.
importDict = {
	"php_javascript_php": ["javascript_main",],
}

#@+node:ekr.20060824111500.1: *4* @ignore Colorizing test scripts
#@+node:ekr.20060824111500.2: *5* @button colorize
w = c.frame.body.bodyCtrl # comment
c.frame.body.colorizer.colorize(p)
names = w.tag_names()
for name in names:
    theList = w.tag_ranges(name)
    if theList:
        print 'tag',name,len(theList)
#@+node:ekr.20060824111500.3: *5* @button red:f1
w = c.frame.body.bodyCtrl
w.tag_configure('red',background='red')
w.tag_add('red','insert-1c','insert+1c')
w.tag_add('f1','insert-1c','insert+1c')
#@+node:ekr.20060824111500.4: *5* @button blue:f2
w = c.frame.body.bodyCtrl
w.tag_configure('blue',background='blue')
w.tag_add('blue','insert-1c','insert+1c')
w.tag_add('f2','insert-1c','insert+1c')
#@+node:ekr.20060824111500.5: *5* @button print tags
w = c.frame.body.bodyCtrl
names = w.tag_names()
print '-' * 20
total = 0
for name in names:
    theList = w.tag_ranges(name)
    if theList:
        print name,len(theList)/2
        total += len(theList)/2
print 'total tag ranges',total
#@+node:ekr.20060824111500.6: *5* @button remove tags
w = c.frame.body.bodyCtrl
names = w.tag_names()
for name in names:
    theList = w.tag_ranges(name)
    if theList:
        print 'removing',name,len(theList)
        while theList:
            a,b = theList[0],theList[1]
            w.tag_remove(name,theList[0],theList[1])
            theList = theList[2:]
#@+node:ekr.20060824111500.7: *5* @button print f1
w = c.frame.body.bodyCtrl

def f1(a,b):
    print 'f1','a',a,'b',b

theList = w.tag_ranges('f1')
while theList:
    a,b = theList[0],theList[1]
    f1(a,b)
    theList = theList[2:]
#@+node:ekr.20060824111500.8: *5* test
@color

abdddddddddddddc
xyz

<< section ref >>

@nocolor
#@+node:ekr.20060824111500.9: *5* latex keywords
#If you see two idenitical words, with minor capitalization differences
#DO NOT ASSUME that they are the same word. For example \vert produces
#a single vertical line and \Vert produces a double vertical line

latex_special_keyword_characters = "@(){}%"

# This is a comment.
'This is a string' # One more comment. Still fast.  Oh joy.

latex_keywords = [
    #special keyworlds.
    "\\%", # 11/9/03
    "\\@", "\\(", "\\)", "\\{", "\\}",
    #A
    "\\acute", "\\addcontentsline", "\\addtocontents", "\\addtocounter", "\\address",
    "\\addtolength", "\\addvspace", "\\AE", "\\ae", "\\aleph", "\\alph", "\\angle",
    "\\appendix", 
    "\\approx", "\\arabic", "\\arccos", "\\arcsin", "\\arctan", "\\ast", "\\author",
    #B
    "\\b", "\\backmatter", "\\backslash", "\\bar", "\\baselineskip", "\\baselinestretch",
    "\\begin", "\\beta", "\\bezier", "\\bf", "\\bfseries", "\\bibitem", "\\bigcap",
    "\\bigcup", "\\bigodot", "\\bigoplus", "\\bigotimes", "\\bigskip", "\\biguplus",
    "\\bigvee", "\\bigwedge", "\\bmod", "\\boldmath", "\\Box", "\\breve", "\\bullet",
    #C
    "\\c", "\\cal", "\\caption", "\\cdot", "\\cdots", "\\centering", "\\chapter",
    "\\check", "\\chi", "\\circ", "\\circle", "\\cite", "\\cleardoublepage", "\\clearpage",
    "\\cline", "\\closing", "\\clubsuit", "\\coprod", "\\copywright", "\\cos", "\\cosh",
    "\\cot", "\\coth", "csc",
    #D
    "\\d", "\\dag", "\\dashbox", "\\date", "\\ddag", "\\ddot", "\\ddots", "\\decl",
    "\\deg", "\\Delta", 
    "\\delta", "\\depthits", "\\det", 
    "\\DH", "\\dh", "\\Diamond", "\\diamondsuit", "\\dim", "\\div", "\\DJ", "\\dj",
    "\\documentclass", "\\documentstyle", 
    "\\dot", "\\dotfil", "\\downarrow",
    #E
    "\\ell", "\\em", "\\emph", "\\end", "\\enlargethispage", "\\ensuremath",
    "\\enumi", "\\enuii", "\\enumiii", "\\enuiv", "\\epsilon", "\\equation", "\\equiv",
    "\\eta", "\\example", "\\exists", "\\exp",
    #F
    "\\fbox", "\\figure", "\\flat", "\\flushbottom", "\\fnsymbol", "\\footnote",
    "\\footnotemark", "\\fotenotesize", 
    "\\footnotetext", "\\forall", "\\frac", "\\frame", "\\framebox", "\\frenchspacing",
    "\\frontmatter",
    #G
    "\\Gamma", "\\gamma", "\\gcd", "\\geq", "\\gg", "\\grave", "\\guillemotleft", 
    "\\guillemotright", "\\guilsinglleft", "\\guilsinglright",
    #H
    "\\H", "\\hat", "\\hbar", "\\heartsuit", "\\heightits", "\\hfill", "\\hline", "\\hom",
    "\\hrulefill", "\\hspace", "\\huge", "\\Huge", "\\hyphenation"
    #I
    "\\Im", "\\imath", "\\include", "includeonly", "indent", "\\index", "\\inf", "\\infty", 
    "\\input", "\\int", "\\iota", "\\it", "\\item", "\\itshape",
    #J
    "\\jmath", "\\Join",
    #K
    "\\k", "\\kappa", "\\ker", "\\kill",
    #L
    "\\label", "\\Lambda", "\\lambda", "\\langle", "\\large", "\\Large", "\\LARGE", 
    "\\LaTeX", "\\LaTeXe", 
    "\\ldots", "\\leadsto", "\\left", "\\Leftarrow", "\\leftarrow", "\\lefteqn", "\\leq",
    "\\lg", "\\lhd", "\\lim", "\\liminf", "\\limsup", "\\line", "\\linebreak", 
    "\\linethickness", "\\linewidth", "\\listfiles",
    "\\ll", "\\ln", "\\location", "\\log", "\\Longleftarrow", "\\longleftarrow", 
    "\\Longrightarrow", "longrightarrow",
    #M
    "\\mainmatter", "\\makebox", "\\makeglossary", "\\makeindex","\\maketitle", "\\markboth", "\\markright",
    "\\mathbf", "\\mathcal", "\\mathit", "\\mathnormal", "\\mathop",
    "\\mathrm", "\\mathsf", "\\mathtt", "\\max", "\\mbox", "\\mdseries", "\\medskip",
    "\\mho", "\\min", "\\mp", "\\mpfootnote", "\\mu", "\\multicolumn", "\\multiput",
    #N
    "\\nabla", "\\natural", "\\nearrow", "\\neq", "\\newcommand", "\\newcounter", 
    "\\newenvironment", "\\newfont",
    "\\newlength", "\\newline", "\\newpage", "\\newsavebox", "\\newtheorem", "\\NG", "\\ng",
    "\\nocite", "\\noindent", "\\nolinbreak", "\\nopagebreak", "\\normalsize",
    "\\not", "\\nu", "nwarrow",
    #O
    "\\Omega", "\\omega", "\\onecolumn", "\\oint", "\\opening", "\\oval", 
    "\\overbrace", "\\overline",
    #P
    "\\P", "\\page", "\\pagebreak", "\\pagenumbering", "\\pageref", "\\pagestyle", 
    "\\par", "\\parbox", "\\paragraph", "\\parindent", "\\parskip", "\\part", 
    "\\partial", "\\per", "\\Phi", "\\phi", "\\Pi", "\\pi", "\\pm", 
    "\\pmod", "\\pounds", "\\prime", "\\printindex", "\\prod", "\\propto", "\\protext", 
    "\\providecomamnd", "\\Psi", "\\psi", "\\put",
    #Q
    "\\qbezier", "\\quoteblbase", "\\quotesinglbase",
    #R
    "\\r", "\\raggedbottom", "\\raggedleft", "\\raggedright", "\\raisebox", "\\rangle", 
    "\\Re", "\\ref", "\\renewcommand", "\\renewenvironment", "\\rhd", "\\rho", "\\right", 
    "\\Rightarrow", "\\rightarrow", "\\rm", "\\rmfamily",
    "\\Roman", "\\roman", "\\rule", 
    #S
    "\\s", "\\samepage", "\\savebox", "\\sbox", "\\sc", "\\scriptsize", "\\scshape", 
    "\\searrow", "\\sec", "\\section",
    "\\setcounter", "\\setlength", "\\settowidth", "\\settodepth", "\\settoheight", 
    "\\settowidth", "\\sf", "\\sffamily", "\\sharp", "\\shortstack", "\\Sigma", "\\sigma", 
    "\\signature", "\\sim", "\\simeq", "\\sin", "\\sinh", "\\sl", "\\SLiTeX",
    "\\slshape", "\\small", "\\smallskip", "\\spadesuit", "\\sqrt", "\\sqsubset",
    "\\sqsupset", "\\SS",
    "\\stackrel", "\\star", "\\subsection", "\\subset", 
    "\\subsubsection", "\\sum", "\\sup", "\\supressfloats", "\\surd", "\\swarrow",
    #T
    "\\t", "\\table", "\\tableofcontents", "\\tabularnewline", "\\tan", "\\tanh", 
    "\\tau", "\\telephone", "\\TeX", "\\textbf",
    "\\textbullet", "\\textcircled", "\\textcompworkmark", "\\textemdash", 
    "\\textendash", "\\textexclamdown", "\\textheight", "\\textquestiondown", 
    "\\textquoteblleft", "\\textquoteblright", "\\textquoteleft",
    "\\textperiod", "\\textquotebl", "\\textquoteright", "\\textmd", "\\textit", "\\textrm", 
    "\\textsc", "\\textsl", "\\textsf", "\\textsuperscript", "\\texttt", "\\textup",
    "\\textvisiblespace", "\\textwidth", "\\TH", "\\th", "\\thanks", "\\thebibligraphy",
    "\\Theta", "theta", 
    "\\tilde", "\\thinlines", 
    "\\thispagestyle", "\\times", "\\tiny", "\\title", "\\today", "\\totalheightits", 
    "\\triangle", "\\tt", 
    "\\ttfamily", "\\twocoloumn", "\\typeout", "\\typein",
    #U
    "\\u", "\\underbrace", "\\underline", "\\unitlength", "\\unlhd", "\\unrhd", "\\Uparrow",
    "\\uparrow", "\\updownarrow", "\\upshape", "\\Upsilon", "\\upsilon", "\\usebox",
    "\\usecounter", "\\usepackage", 
    #V
    "\\v", "\\value", "\\varepsilon", "\\varphi", "\\varpi", "\\varrho", "\\varsigma", 
    "\\vartheta", "\\vdots", "\\vec", "\\vector", "\\verb", "\\Vert", "\\vert", "\\vfill",
    "\\vline", "\\vphantom", "\\vspace",
    #W
    "\\widehat", "\\widetilde", "\\widthits", "\\wp",
    #X
    "\\Xi", "\\xi",
    #Z
    "\\zeta" ]

# A test at the very end.  This is jolly.

latex_keywords = [
    #special keyworlds.
    "\\%", # 11/9/03
    "\\@", "\\(", "\\)", "\\{", "\\}",
    #A
    "\\acute", "\\addcontentsline", "\\addtocontents", "\\addtocounter", "\\address",
    "\\addtolength", "\\addvspace", "\\AE", "\\ae", "\\aleph", "\\alph", "\\angle",
    "\\appendix", 
    "\\approx", "\\arabic", "\\arccos", "\\arcsin", "\\arctan", "\\ast", "\\author",
    #B
    "\\b", "\\backmatter", "\\backslash", "\\bar", "\\baselineskip", "\\baselinestretch",
    "\\begin", "\\beta", "\\bezier", "\\bf", "\\bfseries", "\\bibitem", "\\bigcap",
    "\\bigcup", "\\bigodot", "\\bigoplus", "\\bigotimes", "\\bigskip", "\\biguplus",
    "\\bigvee", "\\bigwedge", "\\bmod", "\\boldmath", "\\Box", "\\breve", "\\bullet",
    #C
    "\\c", "\\cal", "\\caption", "\\cdot", "\\cdots", "\\centering", "\\chapter",
    "\\check", "\\chi", "\\circ", "\\circle", "\\cite", "\\cleardoublepage", "\\clearpage",
    "\\cline", "\\closing", "\\clubsuit", "\\coprod", "\\copywright", "\\cos", "\\cosh",
    "\\cot", "\\coth", "csc",
    #D
    "\\d", "\\dag", "\\dashbox", "\\date", "\\ddag", "\\ddot", "\\ddots", "\\decl",
    "\\deg", "\\Delta", 
    "\\delta", "\\depthits", "\\det", 
    "\\DH", "\\dh", "\\Diamond", "\\diamondsuit", "\\dim", "\\div", "\\DJ", "\\dj",
    "\\documentclass", "\\documentstyle", 
    "\\dot", "\\dotfil", "\\downarrow",
    #E
    "\\ell", "\\em", "\\emph", "\\end", "\\enlargethispage", "\\ensuremath",
    "\\enumi", "\\enuii", "\\enumiii", "\\enuiv", "\\epsilon", "\\equation", "\\equiv",
    "\\eta", "\\example", "\\exists", "\\exp",
    #F
    "\\fbox", "\\figure", "\\flat", "\\flushbottom", "\\fnsymbol", "\\footnote",
    "\\footnotemark", "\\fotenotesize", 
    "\\footnotetext", "\\forall", "\\frac", "\\frame", "\\framebox", "\\frenchspacing",
    "\\frontmatter",
    #G
    "\\Gamma", "\\gamma", "\\gcd", "\\geq", "\\gg", "\\grave", "\\guillemotleft", 
    "\\guillemotright", "\\guilsinglleft", "\\guilsinglright",
    #H
    "\\H", "\\hat", "\\hbar", "\\heartsuit", "\\heightits", "\\hfill", "\\hline", "\\hom",
    "\\hrulefill", "\\hspace", "\\huge", "\\Huge", "\\hyphenation"
    #I
    "\\Im", "\\imath", "\\include", "includeonly", "indent", "\\index", "\\inf", "\\infty", 
    "\\input", "\\int", "\\iota", "\\it", "\\item", "\\itshape",
    #J
    "\\jmath", "\\Join",
    #K
    "\\k", "\\kappa", "\\ker", "\\kill",
    #L
    "\\label", "\\Lambda", "\\lambda", "\\langle", "\\large", "\\Large", "\\LARGE", 
    "\\LaTeX", "\\LaTeXe", 
    "\\ldots", "\\leadsto", "\\left", "\\Leftarrow", "\\leftarrow", "\\lefteqn", "\\leq",
    "\\lg", "\\lhd", "\\lim", "\\liminf", "\\limsup", "\\line", "\\linebreak", 
    "\\linethickness", "\\linewidth", "\\listfiles",
    "\\ll", "\\ln", "\\location", "\\log", "\\Longleftarrow", "\\longleftarrow", 
    "\\Longrightarrow", "longrightarrow",
    #M
    "\\mainmatter", "\\makebox", "\\makeglossary", "\\makeindex","\\maketitle", "\\markboth", "\\markright",
    "\\mathbf", "\\mathcal", "\\mathit", "\\mathnormal", "\\mathop",
    "\\mathrm", "\\mathsf", "\\mathtt", "\\max", "\\mbox", "\\mdseries", "\\medskip",
    "\\mho", "\\min", "\\mp", "\\mpfootnote", "\\mu", "\\multicolumn", "\\multiput",
    #N
    "\\nabla", "\\natural", "\\nearrow", "\\neq", "\\newcommand", "\\newcounter", 
    "\\newenvironment", "\\newfont",
    "\\newlength", "\\newline", "\\newpage", "\\newsavebox", "\\newtheorem", "\\NG", "\\ng",
    "\\nocite", "\\noindent", "\\nolinbreak", "\\nopagebreak", "\\normalsize",
    "\\not", "\\nu", "nwarrow",
    #O
    "\\Omega", "\\omega", "\\onecolumn", "\\oint", "\\opening", "\\oval", 
    "\\overbrace", "\\overline",
    #P
    "\\P", "\\page", "\\pagebreak", "\\pagenumbering", "\\pageref", "\\pagestyle", 
    "\\par", "\\parbox", "\\paragraph", "\\parindent", "\\parskip", "\\part", 
    "\\partial", "\\per", "\\Phi", "\\phi", "\\Pi", "\\pi", "\\pm", 
    "\\pmod", "\\pounds", "\\prime", "\\printindex", "\\prod", "\\propto", "\\protext", 
    "\\providecomamnd", "\\Psi", "\\psi", "\\put",
    #Q
    "\\qbezier", "\\quoteblbase", "\\quotesinglbase",
    #R
    "\\r", "\\raggedbottom", "\\raggedleft", "\\raggedright", "\\raisebox", "\\rangle", 
    "\\Re", "\\ref", "\\renewcommand", "\\renewenvironment", "\\rhd", "\\rho", "\\right", 
    "\\Rightarrow", "\\rightarrow", "\\rm", "\\rmfamily",
    "\\Roman", "\\roman", "\\rule", 
    #S
    "\\s", "\\samepage", "\\savebox", "\\sbox", "\\sc", "\\scriptsize", "\\scshape", 
    "\\searrow", "\\sec", "\\section",
    "\\setcounter", "\\setlength", "\\settowidth", "\\settodepth", "\\settoheight", 
    "\\settowidth", "\\sf", "\\sffamily", "\\sharp", "\\shortstack", "\\Sigma", "\\sigma", 
    "\\signature", "\\sim", "\\simeq", "\\sin", "\\sinh", "\\sl", "\\SLiTeX",
    "\\slshape", "\\small", "\\smallskip", "\\spadesuit", "\\sqrt", "\\sqsubset",
    "\\sqsupset", "\\SS",
    "\\stackrel", "\\star", "\\subsection", "\\subset", 
    "\\subsubsection", "\\sum", "\\sup", "\\supressfloats", "\\surd", "\\swarrow",
    #T
    "\\t", "\\table", "\\tableofcontents", "\\tabularnewline", "\\tan", "\\tanh", 
    "\\tau", "\\telephone", "\\TeX", "\\textbf",
    "\\textbullet", "\\textcircled", "\\textcompworkmark", "\\textemdash", 
    "\\textendash", "\\textexclamdown", "\\textheight", "\\textquestiondown", 
    "\\textquoteblleft", "\\textquoteblright", "\\textquoteleft",
    "\\textperiod", "\\textquotebl", "\\textquoteright", "\\textmd", "\\textit", "\\textrm", 
    "\\textsc", "\\textsl", "\\textsf", "\\textsuperscript", "\\texttt", "\\textup",
    "\\textvisiblespace", "\\textwidth", "\\TH", "\\th", "\\thanks", "\\thebibligraphy",
    "\\Theta", "theta", 
    "\\tilde", "\\thinlines", 
    "\\thispagestyle", "\\times", "\\tiny", "\\title", "\\today", "\\totalheightits", 
    "\\triangle", "\\tt", 
    "\\ttfamily", "\\twocoloumn", "\\typeout", "\\typein",
    #U
    "\\u", "\\underbrace", "\\underline", "\\unitlength", "\\unlhd", "\\unrhd", "\\Uparrow",
    "\\uparrow", "\\updownarrow", "\\upshape", "\\Upsilon", "\\upsilon", "\\usebox",
    "\\usecounter", "\\usepackage", 
    #V
    "\\v", "\\value", "\\varepsilon", "\\varphi", "\\varpi", "\\varrho", "\\varsigma", 
    "\\vartheta", "\\vdots", "\\vec", "\\vector", "\\verb", "\\Vert", "\\vert", "\\vfill",
    "\\vline", "\\vphantom", "\\vspace",
    #W
    "\\widehat", "\\widetilde", "\\widthits", "\\wp",
    #X
    "\\Xi", "\\xi",
    #Z
    "\\zeta" ]

# A test at the very end.  This is jolly.

latex_keywords = [
    #special keyworlds.
    "\\%", # 11/9/03
    "\\@", "\\(", "\\)", "\\{", "\\}",
    #A
    "\\acute", "\\addcontentsline", "\\addtocontents", "\\addtocounter", "\\address",
    "\\addtolength", "\\addvspace", "\\AE", "\\ae", "\\aleph", "\\alph", "\\angle",
    "\\appendix", 
    "\\approx", "\\arabic", "\\arccos", "\\arcsin", "\\arctan", "\\ast", "\\author",
    #B
    "\\b", "\\backmatter", "\\backslash", "\\bar", "\\baselineskip", "\\baselinestretch",
    "\\begin", "\\beta", "\\bezier", "\\bf", "\\bfseries", "\\bibitem", "\\bigcap",
    "\\bigcup", "\\bigodot", "\\bigoplus", "\\bigotimes", "\\bigskip", "\\biguplus",
    "\\bigvee", "\\bigwedge", "\\bmod", "\\boldmath", "\\Box", "\\breve", "\\bullet",
    #C
    "\\c", "\\cal", "\\caption", "\\cdot", "\\cdots", "\\centering", "\\chapter",
    "\\check", "\\chi", "\\circ", "\\circle", "\\cite", "\\cleardoublepage", "\\clearpage",
    "\\cline", "\\closing", "\\clubsuit", "\\coprod", "\\copywright", "\\cos", "\\cosh",
    "\\cot", "\\coth", "csc",
    #D
    "\\d", "\\dag", "\\dashbox", "\\date", "\\ddag", "\\ddot", "\\ddots", "\\decl",
    "\\deg", "\\Delta", 
    "\\delta", "\\depthits", "\\det", 
    "\\DH", "\\dh", "\\Diamond", "\\diamondsuit", "\\dim", "\\div", "\\DJ", "\\dj",
    "\\documentclass", "\\documentstyle", 
    "\\dot", "\\dotfil", "\\downarrow",
    #E
    "\\ell", "\\em", "\\emph", "\\end", "\\enlargethispage", "\\ensuremath",
    "\\enumi", "\\enuii", "\\enumiii", "\\enuiv", "\\epsilon", "\\equation", "\\equiv",
    "\\eta", "\\example", "\\exists", "\\exp",
    #F
    "\\fbox", "\\figure", "\\flat", "\\flushbottom", "\\fnsymbol", "\\footnote",
    "\\footnotemark", "\\fotenotesize", 
    "\\footnotetext", "\\forall", "\\frac", "\\frame", "\\framebox", "\\frenchspacing",
    "\\frontmatter",
    #G
    "\\Gamma", "\\gamma", "\\gcd", "\\geq", "\\gg", "\\grave", "\\guillemotleft", 
    "\\guillemotright", "\\guilsinglleft", "\\guilsinglright",
    #H
    "\\H", "\\hat", "\\hbar", "\\heartsuit", "\\heightits", "\\hfill", "\\hline", "\\hom",
    "\\hrulefill", "\\hspace", "\\huge", "\\Huge", "\\hyphenation"
    #I
    "\\Im", "\\imath", "\\include", "includeonly", "indent", "\\index", "\\inf", "\\infty", 
    "\\input", "\\int", "\\iota", "\\it", "\\item", "\\itshape",
    #J
    "\\jmath", "\\Join",
    #K
    "\\k", "\\kappa", "\\ker", "\\kill",
    #L
    "\\label", "\\Lambda", "\\lambda", "\\langle", "\\large", "\\Large", "\\LARGE", 
    "\\LaTeX", "\\LaTeXe", 
    "\\ldots", "\\leadsto", "\\left", "\\Leftarrow", "\\leftarrow", "\\lefteqn", "\\leq",
    "\\lg", "\\lhd", "\\lim", "\\liminf", "\\limsup", "\\line", "\\linebreak", 
    "\\linethickness", "\\linewidth", "\\listfiles",
    "\\ll", "\\ln", "\\location", "\\log", "\\Longleftarrow", "\\longleftarrow", 
    "\\Longrightarrow", "longrightarrow",
    #M
    "\\mainmatter", "\\makebox", "\\makeglossary", "\\makeindex","\\maketitle", "\\markboth", "\\markright",
    "\\mathbf", "\\mathcal", "\\mathit", "\\mathnormal", "\\mathop",
    "\\mathrm", "\\mathsf", "\\mathtt", "\\max", "\\mbox", "\\mdseries", "\\medskip",
    "\\mho", "\\min", "\\mp", "\\mpfootnote", "\\mu", "\\multicolumn", "\\multiput",
    #N
    "\\nabla", "\\natural", "\\nearrow", "\\neq", "\\newcommand", "\\newcounter", 
    "\\newenvironment", "\\newfont",
    "\\newlength", "\\newline", "\\newpage", "\\newsavebox", "\\newtheorem", "\\NG", "\\ng",
    "\\nocite", "\\noindent", "\\nolinbreak", "\\nopagebreak", "\\normalsize",
    "\\not", "\\nu", "nwarrow",
    #O
    "\\Omega", "\\omega", "\\onecolumn", "\\oint", "\\opening", "\\oval", 
    "\\overbrace", "\\overline",
    #P
    "\\P", "\\page", "\\pagebreak", "\\pagenumbering", "\\pageref", "\\pagestyle", 
    "\\par", "\\parbox", "\\paragraph", "\\parindent", "\\parskip", "\\part", 
    "\\partial", "\\per", "\\Phi", "\\phi", "\\Pi", "\\pi", "\\pm", 
    "\\pmod", "\\pounds", "\\prime", "\\printindex", "\\prod", "\\propto", "\\protext", 
    "\\providecomamnd", "\\Psi", "\\psi", "\\put",
    #Q
    "\\qbezier", "\\quoteblbase", "\\quotesinglbase",
    #R
    "\\r", "\\raggedbottom", "\\raggedleft", "\\raggedright", "\\raisebox", "\\rangle", 
    "\\Re", "\\ref", "\\renewcommand", "\\renewenvironment", "\\rhd", "\\rho", "\\right", 
    "\\Rightarrow", "\\rightarrow", "\\rm", "\\rmfamily",
    "\\Roman", "\\roman", "\\rule", 
    #S
    "\\s", "\\samepage", "\\savebox", "\\sbox", "\\sc", "\\scriptsize", "\\scshape", 
    "\\searrow", "\\sec", "\\section",
    "\\setcounter", "\\setlength", "\\settowidth", "\\settodepth", "\\settoheight", 
    "\\settowidth", "\\sf", "\\sffamily", "\\sharp", "\\shortstack", "\\Sigma", "\\sigma", 
    "\\signature", "\\sim", "\\simeq", "\\sin", "\\sinh", "\\sl", "\\SLiTeX",
    "\\slshape", "\\small", "\\smallskip", "\\spadesuit", "\\sqrt", "\\sqsubset",
    "\\sqsupset", "\\SS",
    "\\stackrel", "\\star", "\\subsection", "\\subset", 
    "\\subsubsection", "\\sum", "\\sup", "\\supressfloats", "\\surd", "\\swarrow",
    #T
    "\\t", "\\table", "\\tableofcontents", "\\tabularnewline", "\\tan", "\\tanh", 
    "\\tau", "\\telephone", "\\TeX", "\\textbf",
    "\\textbullet", "\\textcircled", "\\textcompworkmark", "\\textemdash", 
    "\\textendash", "\\textexclamdown", "\\textheight", "\\textquestiondown", 
    "\\textquoteblleft", "\\textquoteblright", "\\textquoteleft",
    "\\textperiod", "\\textquotebl", "\\textquoteright", "\\textmd", "\\textit", "\\textrm", 
    "\\textsc", "\\textsl", "\\textsf", "\\textsuperscript", "\\texttt", "\\textup",
    "\\textvisiblespace", "\\textwidth", "\\TH", "\\th", "\\thanks", "\\thebibligraphy",
    "\\Theta", "theta", 
    "\\tilde", "\\thinlines", 
    "\\thispagestyle", "\\times", "\\tiny", "\\title", "\\today", "\\totalheightits", 
    "\\triangle", "\\tt", 
    "\\ttfamily", "\\twocoloumn", "\\typeout", "\\typein",
    #U
    "\\u", "\\underbrace", "\\underline", "\\unitlength", "\\unlhd", "\\unrhd", "\\Uparrow",
    "\\uparrow", "\\updownarrow", "\\upshape", "\\Upsilon", "\\upsilon", "\\usebox",
    "\\usecounter", "\\usepackage", 
    #V
    "\\v", "\\value", "\\varepsilon", "\\varphi", "\\varpi", "\\varrho", "\\varsigma", 
    "\\vartheta", "\\vdots", "\\vec", "\\vector", "\\verb", "\\Vert", "\\vert", "\\vfill",
    "\\vline", "\\vphantom", "\\vspace",
    #W
    "\\widehat", "\\widetilde", "\\widthits", "\\wp", # This is a test.
    #X
    "\\Xi", "\\xi",
    #Z
    "\\zeta" ]

# A test at the very end.  This is jolly.
#@+node:ekr.20060824111500.107: *4* errors
@nocolor
convert: applescript
SAXParseException: c:\prog\tigris-cvs\leo\modes\applescript.xml:44:23: not well-formed (invalid token)

convert: bibtex
SAXParseException: c:\prog\tigris-cvs\leo\modes\bibtex.xml:1057:48: duplicate attribute

convert: cil
XML error: end keywords not matched by start keywords

convert: pl1
SAXParseException: c:\prog\tigris-cvs\leo\modes\pl1.xml:45:23: not well-formed (invalid token)

convert: shell
SAXParseException: c:\prog\tigris-cvs\leo\modes\shell.xml:61:26: not well-formed (invalid token)

convert: shellscript
SAXParseException: c:\prog\tigris-cvs\leo\modes\shellscript.xml:61:26: not well-formed (invalid token)

convert: ssharp
SAXParseException: c:\prog\tigris-cvs\leo\modes\ssharp.xml:34:19: not well-formed (invalid token)

« (bad character) U+00AB  &#xAB;
» (bad character)       &#xBB;

convert: text
Exception creating c:\prog\tigris-cvs\leo\modes\text..py
Traceback (most recent call last):

  File "<string>", line 1179, in ?

  File "<string>", line 41, in convert

  File "<string>", line 560, in write

  File "<string>", line 541, in putRules

  File "<string>", line 294, in putRule

  File "<string>", line 516, in putTerminate

TypeError: int argument required
#@+node:ekr.20060824111500.106: *4* import from modes
import glob

print '-' * 20
path = r'c:\prog\tigris-cvs\leo\modes'

errors = ['cil',] # End keyword not matched by start.

if 0:
    files = ['python','php']
else:
    files = glob.glob(r'c:\prog\tigris-cvs\leo\modes\*.py')
    files = [g.os_path_split(f)[1] for f in files]
    files = [g.os_path_splitext(f)[0] for f in files]
    # for f in files: print str(f)

if 1:
    good, bad, skipped = 0,0,0
    for modeName in (files):
        if modeName in errors:
            skipped += 1 ; continue
        mode = g.importFromPath (modeName,path)
        if mode:
            good += 1
            if 0:
                for s in ('properties','rulesDict','importDict'):
                    print hasattr(mode,s),modeName,s
        else: bad += 1
    print 'good: %d, bad: %d, skipped: %d' % (good,bad,skipped)
#@+node:ekr.20060824111500.14: *4* jEdit docs...
@nocolor
#@+node:ekr.20060824111500.15: *5* @url http://www.jedit.org/42docs/users-guide/writing-modes-part.html
#@+node:ekr.20060824111500.16: *5* Rule ordering
You might encounter this very common pitfall when writing your own modes.

Since jEdit checks buffer text against parser rules in the order they appear in
the ruleset, more specific rules must be placed before generalized ones,
otherwise the generalized rules will catch everything.
#@+node:ekr.20060824111500.17: *5* Attributes
#@+node:ekr.20060824111500.18: *6*  Documentation of attributes
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.23: *7* AT_CHAR (int)
The number of characters to terminate after.

For terminate only.
#@+node:ekr.20060824111500.24: *7* EXCLUDE_MATCH (bool)
If set to TRUE, the match will not be highlighted, only the text before it will.
#@+node:ekr.20060824111500.25: *7* NO_xxx
#@+node:ekr.20060824111500.26: *8* NO_WORD_BREAK (bool)
If set to TRUE, the span will not cross word breaks.

For 'span' only.
#@+node:ekr.20060824111500.27: *8* NO_LINE_BREAK (bool)
If set to TRUE, the span will not cross line breaks.

For 'span' only.
#@+node:ekr.20060824111500.28: *8* NO_ESCAPE (bool)
If set to TRUE, the ruleset's escape character will have no effect before the
span's end string. Otherwise, the presence of the escape character will cause
that occurrence of the end string to be ignored.

For 'span' only.
#@+node:ekr.20060824111500.29: *7*  For 'rules' only
#@+node:ekr.20060824111500.30: *8* DEFAULT
The token type for text which doesn't match any specific rule. Default is NULL.
See the section called “Token Types” for a list of token types.
#@+node:ekr.20060824111500.31: *8* DIGIT_RE (re) & HIGHLIGHT_DIGITS  (bool)
If the HIGHLIGHT_DIGITS attribute is set to TRUE, jEdit will attempt to highlight numbers in this ruleset.

Any word consisting entirely of digits (0-9) will be highlighted with the DIGIT token type.

A word that contains other letters in addition to digits will be highlighted with the DIGIT token type only if it matches the regular expression specified in the DIGIT_RE attribute. If this attribute is not specified, it will not be highlighted.

Here is an example DIGIT_RE regular expression that highlights Java-style numeric literals (normal numbers, hexadecimals prefixed with 0x, numbers suffixed with various type indicators, and floating point literals containing an exponent):

DIGIT_RE="(0x[[:xdigit:]]+|[[:digit:]]+(e[[:digit:]]*)?)[lLdDfF]?"
#@+node:ekr.20060824111500.32: *8* IGNORE_CASE (bool)
If set to FALSE, matches will be case sensitive.
Otherwise, case will not matter. Default is TRUE.
#@+node:ekr.20060824111500.33: *8* SET
The name of this ruleset. All rulesets other than the first must have a name.
#@+node:ekr.20060824111500.34: *8* NO_WORD_SEP
Any non-alphanumeric character not in this list is treated as a word separator for the purposes of syntax highlighting.
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.36: *7* HASH_CHAR (char) For 'regx'
Atribute            Elements used in
--------            ----------------
HASH_CHAR           EOL_SPAN_REGEXP,SPAN_REGEXP,SEQ_REGEXP

It must be set to the first character that the regular expression matches. This
rules out using regular expressions which can match more than one character at
the start position. The regular expression match cannot span more than one line,
either.
#@+node:ekr.20060824111500.37: *7* TYPE (Token Types)
The token type to highlight the text with.

Parser rules can highlight tokens using any of the following token types:

NULL - no special highlighting
COMMENT1,COMMENT2,COMMENT3,COMMENT4
FUNCTION
KEYWORD1,KEYWORD2,KEYWORD3,KEYWORD4
LABEL
LITERAL1,LITERAL2,LITERAL3,LITERAL4
MARKUP
OPERATOR
#@+node:ekr.20060824111500.38: *6* Attributes for 'begin' & 'end'
Atribute            Elements used in
--------            ----------------
AT_LINE_START       BEGIN,END
AT_WHITESPACE_END   BEGIN,END
AT_WORD_START       BEGIN,END
#@+node:ekr.20060824111500.39: *6* Attributes for 'import': delegate
The only required attribute DELEGATE must be set to the name of a ruleset. To
import a ruleset defined in the current mode, just specify its name. To import a
ruleset defined in another mode, specify a name of the form mode::ruleset. Note
that the first (unnamed) ruleset in a mode is called “MAIN”.

One quirk is that the definition of the imported ruleset is not copied to the
location of the IMPORT tag, but rather to the end of the containing ruleset.
This has implications with rule-ordering; see the section called “Rule Ordering
Requirements”.
#@+node:ekr.20060824111500.40: *6* Attributes for 'mark'
Atribute            Elements used in
--------            ----------------
AT_LINE_START       MARK_FOLLOWING,MARK_PREVIOUS
AT_WHITESPACE_END   MARK_FOLLOWING,MARK_PREVIOUS
AT_WORD_START       MARK_FOLLOWING,MARK_PREVIOUS 
EXCLUDE_MATCH       MARK_FOLLOWING,MARK_PREVIOUS
TYPE                MARK_FOLLOWING,MARK_PREVIOUS
#@+node:ekr.20060824111500.41: *6* Attributes for 'property': name, value
Atribute            Elements used in
--------            ----------------
NAME                PROPERTY
VALUE               PROPERTY
#@+node:ekr.20060824111500.42: *6* Attributes for 'rules'
Atribute            Elements used in
--------            ----------------
DEFAULT             RULES
DIGIT_RE            RULES
HIGHLIGHT_DIGITS    RULES
IGNORE_CASE         RULES
SET                 RULES
NO_WORD_SEP         RULES

The RULES tag supports the following attributes, all of which are optional: 

SET the name of this ruleset. All rulesets other than the first must have a
name.

IGNORE_CASE if set to FALSE, matches will be case sensitive. Otherwise, case
will not matter. Default is TRUE.

NO_WORD_SEP Any non-alphanumeric character not in this list is treated as a word
separator for the purposes of syntax highlighting.

DEFAULT The token type for text which doesn't match any specific rule. Default
is NULL. See the section called “Token Types” for a list of token types.

HIGHLIGHT_DIGITS DIGIT_RE

If the HIGHLIGHT_DIGITS attribute is set to TRUE, jEdit will attempt to
highlight numbers in this ruleset.

Any word consisting entirely of digits (0-9) will be highlighted with the DIGIT
token type. A word that contains other letters in addition to digits will be
highlighted with the DIGIT token type only if it matches the regular expression
specified in the DIGIT_RE attribute. If this attribute is not specified, it will
not be highlighted.
#@+node:ekr.20060824111500.43: *6* Attributes for 'span'
all_spans   = EOL_SPAN,EOL_SPAN_REGEXP,SPAN,SPAN_REGEXP
plain_spans = SPAN,SPAN_REGEXP
regx_spans  = EOL_SPAN_REGEXP,SPAN_REGEXP

Atribute            Elements used in
--------            ----------------
AT_LINE_START       all_spans
AT_WHITESPACE_END   all_spans
AT_WORD_START       all_spans
DELEGATE            all_spans
EXCLUDE_MATCH       all_spans
HASH_CHAR           regx_spans  (must be specified)
NO_ESCAPE           plain_spans
NO_LINE_BREAK       plain_spans
NO_WORD_BREAK       plain_spans
TYPE                all_spans
#@+node:ekr.20060824111500.44: *6* Attributes for 'seq'
Atribute            Elements used in
--------            ----------------
AT_LINE_START       SEQ,SEQ_REGEXP
AT_WHITESPACE_END   SEQ,SEQ_REGEXP
AT_WORD_START       SEQ,SEQ_REGEXP
DELEGATE            SEQ,SEQ_REGEXP
HASH_CHAR           SEQ_REGEXP (must be specified)
TYPE                SEQ,SEQ_REGEXP
#@+node:ekr.20060824111500.45: *5* Elements
@language html
@color
#@+node:ekr.20060824111500.46: *6* Spans and seqs...
#@+node:ekr.20060824111500.47: *7* eol_span
An EOL_SPAN is similar to a SPAN except that highlighting stops at the end of
the line, and no end sequence needs to be specified. The text to match is
specified between the opening and closing EOL_SPAN tags.

The following attributes are supported:

TYPE - The token type to highlight the span with. See the section called “Token
Types” for a list of token types.

AT_LINE_START - If set to TRUE, the span will only be highlighted if the start
sequence occurs at the beginning of a line.

AT_WHITESPACE_END - If set to TRUE, the span will only be highlighted if the
sequence is the first non-whitespace text in the line.

AT_WORD_START - If set to TRUE, the span will only be highlighted if the start
sequence occurs at the beginning of a word.

DELEGATE - text inside the span will be highlighted with the specified ruleset.
To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

EXCLUDE_MATCH - If set to TRUE, the start and end sequences will not be
highlighted, only the text between them will.

Here is an EOL_SPAN that highlights C++ comments:

<EOL_SPAN TYPE="COMMENT1">//</EOL_SPAN>
#@+node:ekr.20060824111500.48: *7* eol_span_regexp
The EOL_SPAN_REGEXP rule is similar to the EOL_SPAN rule except the match
sequence is taken to be a regular expression.

In addition to the attributes supported by the EOL_SPAN tag, the HASH_CHAR
attribute must be specified. It must be set to the first character that the
regular expression matches. This rules out using regular expressions which can
match more than one character at the start position. The regular expression
match cannot span more than one line, either.

Here is an EOL_SPAN_REGEXP that highlights MS-DOS batch file comments, which
start with REM, followed by any whitespace character, and extend until the end
of the line:

<EOL_SPAN_REGEXP AT_WHITESPACE_END="TRUE" HASH_CHAR="R" TYPE="COMMENT1">REM\s</EOL_SPAN_REGEXP>
#@+node:ekr.20060824111500.49: *7* mark_following
The MARK_FOLLOWING rule, which must be placed inside a RULES tag, highlights
from the start of the match to the next syntax token. The text to match is
specified between opening and closing MARK_FOLLOWING tags.

The following attributes are supported:

TYPE - The token type to highlight the text with. See the section called “Token
Types” for a list of token types.

AT_LINE_START - If set to TRUE, the sequence will only be highlighted if it
occurs at the beginning of a line.

AT_WHITESPACE_END - If set to TRUE, the sequence will only be highlighted if it
is the first non-whitespace text in the line.

AT_WORD_START - If set to TRUE, the sequence will only be highlighted if it
occurs at the beginning of a word.

EXCLUDE_MATCH - If set to TRUE, the match will not be highlighted, only the text
after it will.

Here is a rule that highlights variables in Unix shell scripts (“$CLASSPATH”,“$IFS”, etc):

<MARK_FOLLOWING TYPE="KEYWORD2">$</MARK_FOLLOWING>
#@+node:ekr.20060824111500.50: *7* mark_previous
The MARK_PREVIOUS rule, which must be placed inside a RULES tag, highlights from
the end of the previous syntax token to the matched text. The text to match is
specified between opening and closing MARK_PREVIOUS tags. The following
attributes are supported:

TYPE - The token type to highlight the text with. See the section called “Token
Types” for a list of token types.

AT_LINE_START - If set to TRUE, the sequence will only be highlighted if it
occurs at the beginning of a line.

AT_WHITESPACE_END - If set to TRUE, the sequence will only be highlighted if it
is the first non-whitespace text in the line.

AT_WORD_START - If set to TRUE, the sequence will only be highlighted if it
occurs at the beginning of a word.

EXCLUDE_MATCH - If set to TRUE, the match will not be highlighted, only the text
before it will.

Here is a rule that highlights labels in Java mode (for example, “XXX:”):

<MARK_PREVIOUS AT_WHITESPACE_END="TRUE" EXCLUDE_MATCH="TRUE">:</MARK_PREVIOUS>
#@+node:ekr.20060824111500.51: *7* seq
The SEQ rule, which must be placed inside a RULES tag, highlights fixed
sequences of text. The text to highlight is specified between opening and
closing SEQ tags. The following attributes are supported:

TYPE - the token type to highlight the sequence with. See the section called
“Token Types” for a list of token types.

AT_LINE_START - If set to TRUE, the sequence will only be highlighted if it
occurs at the beginning of a line.

AT_WHITESPACE_END - If set to TRUE, the sequence will only be highlighted if it
is the first non-whitespace text in the line.

AT_WORD_START - If set to TRUE, the sequence will only be highlighted if it
occurs at the beginning of a word.

DELEGATE - if this attribute is specified, all text after the sequence will be
highlighted using this ruleset. To delegate to a ruleset defined in the current
mode, just specify its name. To delegate to a ruleset defined in another mode,
specify a name of the form mode::ruleset. Note that the first (unnamed) ruleset
in a mode is called “MAIN”.

The following rules highlight a few Java operators:

<SEQ TYPE="OPERATOR">+</SEQ>
<SEQ TYPE="OPERATOR">-</SEQ>
<SEQ TYPE="OPERATOR">*</SEQ>
<SEQ TYPE="OPERATOR">/</SEQ>
#@+node:ekr.20060824111500.52: *7* seq_regexp
The SEQ_REGEXP rule is similar to the SEQ rule except the match sequence is
taken to be a regular expression.

In addition to the attributes supported by the SEQ tag, the HASH_CHAR attribute
must be specified. It must be set to the first character that the regular
expression matches. This rules out using regular expressions which can match
more than one character at the start position. The regular expression match
cannot span more than one line, either.

Here is an example of a SEQ_REGEXP rule that highlights Perl's matcher
constructions such as m/(.+):(\d+):(.+)/:

<SEQ_REGEXP TYPE="MARKUP"
    HASH_CHAR="m"
    AT_WORD_START="TRUE"
>m([[:punct:]])(?:.*?[^\\])*?\1[sgiexom]*</SEQ_REGEXP>
#@+node:ekr.20060824111500.53: *7* span
The SPAN rule, which must be placed inside a RULES tag, highlights text between
a start and end string. The start and end strings are specified inside child
elements of the SPAN tag. The following attributes are supported:

TYPE - The token type to highlight the span with. See the section called “Token
Types” for a list of token types.

AT_LINE_START - If set to TRUE, the span will only be highlighted if the start
sequence occurs at the beginning of a line.

AT_WHITESPACE_END - If set to TRUE, the span will only be highlighted if the
start sequence is the first non-whitespace text in the line.

AT_WORD_START - If set to TRUE, the span will only be highlighted if the start
sequence occurs at the beginning of a word.

DELEGATE - text inside the span will be highlighted with the specified ruleset.
To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

EXCLUDE_MATCH - If set to TRUE, the start and end sequences will not be
highlighted, only the text between them will.

NO_ESCAPE - If set to TRUE, the ruleset's escape character will have no effect
before the span's end string. Otherwise, the presence of the escape character
will cause that occurrence of the end string to be ignored.

NO_LINE_BREAK - If set to TRUE, the span will not cross line breaks.

NO_WORD_BREAK - If set to TRUE, the span will not cross word breaks.

Note that the AT_LINE_START, AT_WHITESPACE_END and AT_WORD_START attributes can
also be used on the BEGIN and END elements. Setting these attributes to the same
value on both elements has the same effect as setting them on the SPAN element.

Here is a SPAN that highlights Java string literals, which cannot include line breaks:

<SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE">
  <BEGIN>"</BEGIN>
  <END>"</END>
</SPAN>

Here is a SPAN that highlights Java documentation comments by delegating to the “JAVADOC” ruleset defined elsewhere in the current mode:

<SPAN TYPE="COMMENT2" DELEGATE="JAVADOC">
  <BEGIN>/**</BEGIN>
  <END>*/</END>
</SPAN>

Here is a SPAN that highlights HTML cascading stylesheets inside <STYLE> tags by delegating to the main ruleset in the CSS edit mode:

<SPAN TYPE="MARKUP" DELEGATE="css::MAIN">
  <BEGIN>&lt;style&gt;</BEGIN>
  <END>&lt;/style&gt;</END>
</SPAN>
#@+node:ekr.20060824111500.54: *7* span_regexp
The SPAN_REGEXP rule is similar to the SPAN rule except the start sequence is
taken to be a regular expression.

In addition to the attributes supported by the SPAN tag, the HASH_CHAR attribute
must be specified. It must be set to the first character that the regular
expression matches. This rules out using regular expressions which can match
more than one character at the start position. The regular expression match
cannot span more than one line, either.

Any text matched by groups in the BEGIN regular expression is substituted in the
END string. See below for an example of where this is useful. 

Here is a SPAN_REGEXP rule that highlights “read-ins” in shell scripts:

<SPAN_REGEXP HASH_CHAR="<" TYPE="LITERAL1" DELEGATE="LITERAL">
    <BEGIN><![CDATA[<<[[:space:]'"]*([[:alnum:]_]+)[[:space:]'"]*]]></BEGIN>
    <END>$1</END>
</SPAN_REGEXP>

Here is a SPAN_REGEXP rule that highlights constructs placed between <#ftl and
>, as long as the <#ftl is followed by a word break:

<SPAN_REGEXP TYPE="KEYWORD1" HASH_CHAR="&lt;" DELEGATE="EXPRESSION">
    <BEGIN>&lt;#ftl\&gt;</BEGIN>
    <END>&gt;</END>
</SPAN_REGEXP>
#@+node:ekr.20060824111500.55: *6* All others...
#@+node:ekr.20060824111500.56: *7* import
The IMPORT tag, which must be placed inside a RULES tag, loads all rules defined
in a given ruleset into the current ruleset; in other words, it has the same
effect as copying and pasting the imported ruleset.

The only required attribute DELEGATE must be set to the name of a ruleset. To
import a ruleset defined in the current mode, just specify its name. To import a
ruleset defined in another mode, specify a name of the form mode::ruleset. Note
that the first (unnamed) ruleset in a mode is called “MAIN”.

One quirk is that the definition of the imported ruleset is not copied to the
location of the IMPORT tag, but rather to the end of the containing ruleset.
This has implications with rule-ordering; see the section called “Rule Ordering
Requirements”.

Here is an example from the PHP mode, which extends the inline JavaScript
highlighting to support embedded PHP:


   <RULES SET="JAVASCRIPT+PHP">

   <SPAN TYPE="MARKUP" DELEGATE="php::PHP">
       <BEGIN>&lt;?php</BEGIN>
       <END>?&gt;</END>
   </SPAN>

   <SPAN TYPE="MARKUP" DELEGATE="php::PHP">
       <BEGIN>&lt;?</BEGIN>
       <END>?&gt;</END>
   </SPAN>

   <SPAN TYPE="MARKUP" DELEGATE="php::PHP">
       <BEGIN>&lt;%=</BEGIN>
       <END>%&gt;</END>
   </SPAN>

   <IMPORT DELEGATE="javascript::MAIN"/>
</RULES>
#@+node:ekr.20060824111500.57: *7* keywords (done)
The KEYWORDS tag, which must be placed inside a RULES tag and can only appear
once, specifies a list of keywords to highlight. Keywords are similar to SEQs,
except that SEQs match anywhere in the text, whereas keywords only match whole
words. Words are considered to be runs of text separated by non-alphanumeric
characters.

The KEYWORDS tag does not define any attributes.

Each child element of the KEYWORDS tag is an element whose name is a token type,
and whose content is the keyword to highlight.

<KEYWORDS>
  <KEYWORD1>if</KEYWORD1>
  <KEYWORD1>else</KEYWORD1>
  <KEYWORD3>int</KEYWORD3>
  <KEYWORD3>void</KEYWORD3>
</KEYWORDS>
#@+node:ekr.20060824111500.58: *7* mode (done)
Each mode definition must begin with the following:

<?xml version="1.0"?>
<!DOCTYPE MODE SYSTEM "xmode.dtd">

Each mode definition must also contain exactly one MODE tag. All other tags (PROPS, RULES) must be placed inside the MODE tag. The MODE tag does not have any defined attributes. Here is an example:

<MODE>
    ... mode definition goes here ...
</MODE>
#@+node:ekr.20060824111500.59: *7* props & property  (for auto-indent)
The PROPS tag and the PROPERTY tags inside it are used to define mode-specific
properties. Each PROPERTY tag must have a NAME attribute set to the property's
name, and a VALUE attribute with the property's value.

All buffer-local properties listed in the section called “Buffer-Local
Properties” may be given values in edit modes.

The following mode properties specify commenting strings:

* commentEnd - the comment end string, used by the Range Comment command.
* commentStart - the comment start string, used by the Range Comment command.
* lineComment - the line comment string, used by the Line Comment command. 

When performing auto indent, a number of mode properties determine the resulting indent level:

* The line and the one before it are scanned for brackets listed in the
indentCloseBrackets and indentOpenBrackets properties. Opening brackets in the
previous line increase indent.

If lineUpClosingBracket is set to true, then closing brackets on the current
line will line up with the line containing the matching opening bracket. For
example, in Java mode lineUpClosingBracket is set to true, resulting in brackets
being indented like so:

{
    // Code
    {
        // More code
    }
}

If lineUpClosingBracket is set to false, the line after a closing bracket will
be lined up with the line containing the matching opening bracket. For example,
in Lisp mode lineUpClosingBracket is set to false, resulting in brackets being
indented like so:

(foo 'a-parameter
    (crazy-p)
    (bar baz ()))
(print "hello world")

* If the previous line contains no opening brackets, or if the
doubleBracketIndent property is set to true, the previous line is checked
against the regular expressions in the indentNextLine and indentNextLines
properties. If the previous line matches the former, the indent of the current
line is increased and the subsequent line is shifted back again. If the previous
line matches the latter, the indent of the current and subsequent lines is
increased.

In Java mode, for example, the indentNextLine property is set to match control
structures such as “if”, “else”, “while”, and so on.

The doubleBracketIndent property, if set to the default of false, results in code indented like so:

while(objects.hasNext())
{
    Object next = objects.hasNext();
    if(next instanceof Paintable)
        next.paint(g);
}

On the other hand, settings this property to “true” will give the following result:

while(objects.hasNext())
    {
        Object next = objects.hasNext();
        if(next instanceof Paintable)
            next.paint(g);
    }

Here is the complete <PROPS> tag for Java mode:

<PROPS>
    <PROPERTY NAME="commentStart" VALUE="/*" />
    <PROPERTY NAME="commentEnd" VALUE="*/" />
    <PROPERTY NAME="lineComment" VALUE="//" />
    <PROPERTY NAME="wordBreakChars" VALUE=",+-=&lt;&gt;/?^&amp;*" />

    <!-- Auto indent -->
    <PROPERTY NAME="indentOpenBrackets" VALUE="{" />
    <PROPERTY NAME="indentCloseBrackets" VALUE="}" />
    <PROPERTY NAME="indentNextLine"
    	VALUE="\s*(((if|while)\s*\(|else\s*|else\s+if\s*\(|for\s*\(.*\))[^{;]*)" />
    <!-- set this to 'true' if you want to use GNU coding style -->
    <PROPERTY NAME="doubleBracketIndent" VALUE="false" />
    <PROPERTY NAME="lineUpClosingBracket" VALUE="true" />
</PROPS>
#@+node:ekr.20060824111500.60: *7* rules
RULES tags must be placed inside the MODE tag. Each RULES tag defines a ruleset.
A ruleset consists of a number of parser rules, with each parser rule specifying
how to highlight a specific syntax token. There must be at least one ruleset in
each edit mode. There can also be more than one, with different rulesets being
used to highlight different parts of a buffer (for example, in HTML mode, one
rule set highlights HTML tags, and another highlights inline JavaScript). For
information about using more than one ruleset, see the section called “The SPAN
Tag”.

The RULES tag supports the following attributes, all of which are optional: 

SET the name of this ruleset. All rulesets other than the first must have a
name.

IGNORE_CASE if set to FALSE, matches will be case sensitive. Otherwise, case
will not matter. Default is TRUE.

NO_WORD_SEP Any non-alphanumeric character not in this list is treated as a word
separator for the purposes of syntax highlighting.

DEFAULT The token type for text which doesn't match any specific rule. Default
is NULL. See the section called “Token Types” for a list of token types.

HIGHLIGHT_DIGITS DIGIT_RE

If the HIGHLIGHT_DIGITS attribute is set to TRUE, jEdit will attempt to
highlight numbers in this ruleset.

Any word consisting entirely of digits (0-9) will be highlighted with the DIGIT
token type. A word that contains other letters in addition to digits will be
highlighted with the DIGIT token type only if it matches the regular expression
specified in the DIGIT_RE attribute. If this attribute is not specified, it will
not be highlighted.

Here is an example DIGIT_RE regular expression that highlights Java-style
numeric literals (normal numbers, hexadecimals prefixed with 0x, numbers
suffixed with various type indicators, and floating point literals containing an
exponent):

DIGIT_RE="(0x[[:xdigit:]]+|[[:digit:]]+(e[[:digit:]]*)?)[lLdDfF]?"

Here is an example RULES tag:

<RULES IGNORE_CASE="FALSE" HIGHLIGHT_DIGITS="TRUE">
    ... parser rules go here ...
</RULES>
#@+node:ekr.20060824111500.61: *7* terminate
The TERMINATE rule, which must be placed inside a RULES tag, specifies that
parsing should stop after the specified number of characters have been read from
a line.

The number of characters to terminate after should be specified with the AT_CHAR
attribute. Here is an example:

<TERMINATE AT_CHAR="1" />

This rule is used in Patch mode, for example, because only the first character
of each line affects highlighting.
#@+node:ekr.20060824111500.62: *4* Refactored jEdit docs...
@nocolor
#@+node:ekr.20060824111500.63: *5* @url http://www.jedit.org/42docs/users-guide/writing-modes-part.html
#@+node:ekr.20060824111500.64: *5* Rule ordering
You might encounter this very common pitfall when writing your own modes.

Since jEdit checks buffer text against parser rules in the order they appear in
the ruleset, more specific rules must be placed before generalized ones,
otherwise the generalized rules will catch everything.
#@+node:ekr.20060824111500.65: *5* Attributes
#@+node:ekr.20060824111500.66: *6* AT_CHAR (int)
The number of characters to terminate after.

For terminate only.
#@+node:ekr.20060824111500.19: *6* AT_xxx
#@+node:ekr.20060824111500.20: *7* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *7* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *7* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.35: *6* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.24: *6* EXCLUDE_MATCH (bool)
If set to TRUE, the match will not be highlighted, only the text before it will.
#@+node:ekr.20060824111500.67: *6* For rules only
#@+node:ekr.20060824111500.68: *7* DEFAULT (token type)
The token type for text which doesn't match any specific rule. Default is NULL.
See the section called “Token Types” for a list of token types.

For 'rules' only.
#@+node:ekr.20060824111500.69: *7* HIGHLIGHT_DIGITS  &DIGITS_RE (bool)
If the HIGHLIGHT_DIGITS attribute is set to TRUE, jEdit will attempt to highlight numbers in this ruleset.

Any word consisting entirely of digits (0-9) will be highlighted with the DIGIT token type.

A word that contains other letters in addition to digits will be highlighted with the DIGIT token type only if it matches the regular expression specified in the DIGIT_RE attribute. If this attribute is not specified, it will not be highlighted.

For 'rules' only.

Example: a DIGIT_RE regular expression that highlights Java-style
numeric literals (normal numbers, hexadecimals prefixed with 0x, numbers
suffixed with various type indicators, and floating point literals containing an
exponent):

DIGIT_RE="(0x[[:xdigit:]]+|[[:digit:]]+(e[[:digit:]]*)?)[lLdDfF]?"
#@+node:ekr.20060824111500.70: *7* IGNORE_CASE (bool)
If set to FALSE, matches will be case sensitive.
Otherwise, case will not matter. Default is TRUE.

For 'rules' only.
#@+node:ekr.20060824111500.71: *7* SET (string)
The name of this ruleset. All rulesets other than the first must have a name.

For 'rules' only.
#@+node:ekr.20060824111500.72: *7* NO_WORD_SEP
Any non-alphanumeric character not in this list is treated as a word separator
for the purposes of syntax highlighting.

For 'rules' only.
#@+node:ekr.20060824111500.73: *6* HASH_CHAR (char)
The first character that the regular expression matches.

This rules out using regular expressions which can match more than one character
at the start position. The regular expression match cannot span more than one
line, either.

Required for eol_span_regexp, span_regexp, seq_regexp.
#@+node:ekr.20060824111500.74: *6* NAME and VALUE
Each PROPERTY tag must have a NAME attribute set to the property's name, and a
VALUE attribute with the property's value.

For property only.
#@+node:ekr.20060824111500.25: *6* NO_xxx
#@+node:ekr.20060824111500.26: *7* NO_WORD_BREAK (bool)
If set to TRUE, the span will not cross word breaks.

For 'span' only.
#@+node:ekr.20060824111500.27: *7* NO_LINE_BREAK (bool)
If set to TRUE, the span will not cross line breaks.

For 'span' only.
#@+node:ekr.20060824111500.28: *7* NO_ESCAPE (bool)
If set to TRUE, the ruleset's escape character will have no effect before the
span's end string. Otherwise, the presence of the escape character will cause
that occurrence of the end string to be ignored.

For 'span' only.
#@+node:ekr.20060824111500.75: *6* TYPE (Token Types)
The token type to highlight the text with.

Parser rules can highlight tokens using any of the following token types:

NULL - no special highlighting
COMMENT1,COMMENT2,COMMENT3,COMMENT4
FUNCTION
KEYWORD1,KEYWORD2,KEYWORD3,KEYWORD4
LABEL
LITERAL1,LITERAL2,LITERAL3,LITERAL4
MARKUP
OPERATOR
#@+node:ekr.20060824111500.76: *5* Elements (children are attributes)
@language html
@color

All rules must be contained in the RULES element.
#@+node:ekr.20060824111500.77: *6* begin & end
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.78: *6* eol_span (can use DELEGATE)
An EOL_SPAN is similar to a SPAN except that highlighting stops at the end of
the line, and no end sequence needs to be specified. The text to match is
specified between the opening and closing EOL_SPAN tags.

Attributes: TYPE, AT_xxx, DELEGATE, EXCLUDE_MATCH

Here is an EOL_SPAN that highlights C++ comments:

<EOL_SPAN TYPE="COMMENT1">//</EOL_SPAN>
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.24: *7* EXCLUDE_MATCH (bool)
If set to TRUE, the match will not be highlighted, only the text before it will.
#@+node:ekr.20060824111500.79: *6* eol_span_regexp (can use DELEGATE)
The EOL_SPAN_REGEXP rule is similar to the EOL_SPAN rule except the match
sequence is taken to be a regular expression.

Attributes: TYPE, AT_xxx, DELEGATE, EXCLUDE_MATCH, HASH_CHAR(required)

An EOL_SPAN_REGEXP that highlights MS-DOS batch file comments, which
start with REM, followed by any whitespace character, and extend until the end
of the line:

<EOL_SPAN_REGEXP AT_WHITESPACE_END="TRUE" HASH_CHAR="R" TYPE="COMMENT1">REM\s</EOL_SPAN_REGEXP>
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.24: *7* EXCLUDE_MATCH (bool)
If set to TRUE, the match will not be highlighted, only the text before it will.
#@+node:ekr.20060824111500.73: *7* HASH_CHAR (char)
The first character that the regular expression matches.

This rules out using regular expressions which can match more than one character
at the start position. The regular expression match cannot span more than one
line, either.

Required for eol_span_regexp, span_regexp, seq_regexp.
#@+node:ekr.20060824111500.80: *6* import
The IMPORT tag loads all rules defined in a given ruleset into the current
ruleset; in other words, it has the same effect as copying and pasting the
imported ruleset.

Attriubtes: DELEGATE (required)

An example from the PHP mode, which extends the inline JavaScript
highlighting to support embedded PHP:

<RULES SET="JAVASCRIPT+PHP">

   <SPAN TYPE="MARKUP" DELEGATE="php::PHP">
       <BEGIN>&lt;?php</BEGIN>
       <END>?&gt;</END>
   </SPAN>

   <SPAN TYPE="MARKUP" DELEGATE="php::PHP">
       <BEGIN>&lt;?</BEGIN>
       <END>?&gt;</END>
   </SPAN>

   <SPAN TYPE="MARKUP" DELEGATE="php::PHP">
       <BEGIN>&lt;%=</BEGIN>
       <END>%&gt;</END>
   </SPAN>

   <IMPORT DELEGATE="javascript::MAIN"/>
</RULES>
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.81: *6* keywords
The KEYWORDS tag can only appear once. It specifies a list of keywords to
highlight.

Keywords are similar to SEQs, except that SEQs match anywhere in the
text, whereas keywords only match whole words. Words are considered to be runs
of text separated by non-alphanumeric characters.

Attributes:  None.

Each child element of the KEYWORDS tag is an element whose name is a token type,
and whose content is the keyword to highlight.

Example:

<KEYWORDS>
  <KEYWORD1>if</KEYWORD1>
  <KEYWORD1>else</KEYWORD1>
  <KEYWORD3>int</KEYWORD3>
  <KEYWORD3>void</KEYWORD3>
</KEYWORDS>
#@+node:ekr.20060824111500.82: *6* mark_following & mark_previous
The MARK_FOLLOWING rule ighlights from the start of the match to the next syntax
token. The text to match is specified between opening and closing MARK_FOLLOWING
tags.

The MARK_PREVIOUS rule highlights from the end of the previous syntax token to
the matched text. The text to match is specified between opening and closing
MARK_PREVIOUS tags.

Attributes: TYPE, AT_xxx, EXCLUDE_MATCH

Example:

<MARK_FOLLOWING TYPE="KEYWORD2">$</MARK_FOLLOWING>
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.24: *7* EXCLUDE_MATCH (bool)
If set to TRUE, the match will not be highlighted, only the text before it will.
#@+node:ekr.20060824111500.83: *6* mode
Each mode definition must begin with the following:

<?xml version="1.0"?>
<!DOCTYPE MODE SYSTEM "xmode.dtd">

Each mode definition must also contain exactly one MODE tag.  All other tags (PROPS, RULES) must be placed inside the MODE tag.

Attributes: None

<MODE>
    ... mode definition goes here ...
</MODE>
#@+node:ekr.20060824111500.84: *6* props & property
The PROPS tag and the PROPERTY tags define mode-specific properties.

Attributes: NAME, VALUE

All buffer-local properties may be given values in edit modes.

EKR: must support at least commentStart, commentEnd, lineComment, and wordBreakChars attributes.

Here is the complete <PROPS> tag for Java mode:

<PROPS>
    <PROPERTY NAME="commentStart" VALUE="/*" />
    <PROPERTY NAME="commentEnd" VALUE="*/" />
    <PROPERTY NAME="lineComment" VALUE="//" />
    <PROPERTY NAME="wordBreakChars" VALUE=",+-=&lt;&gt;/?^&amp;*" />

    <!-- Auto indent -->
    <PROPERTY NAME="indentOpenBrackets" VALUE="{" />
    <PROPERTY NAME="indentCloseBrackets" VALUE="}" />
    <PROPERTY NAME="indentNextLine"
    	VALUE="\s*(((if|while)\s*\(|else\s*|else\s+if\s*\(|for\s*\(.*\))[^{;]*)" />
    <!-- set this to 'true' if you want to use GNU coding style -->
    <PROPERTY NAME="doubleBracketIndent" VALUE="false" />
    <PROPERTY NAME="lineUpClosingBracket" VALUE="true" />
</PROPS>
#@+node:ekr.20060824111500.74: *7* NAME and VALUE
Each PROPERTY tag must have a NAME attribute set to the property's name, and a
VALUE attribute with the property's value.

For property only.
#@+node:ekr.20060824111500.85: *7* Properties for comment strings
The following mode properties specify commenting strings:

commentEnd - the comment end string, used by the Range Comment command.

commentStart - the comment start string, used by the Range Comment command.

lineComment - the line comment string, used by the Line Comment command. 
#@+node:ekr.20060824111500.86: *7* Properties for auto-indent
When performing auto indent, a number of mode properties determine the resulting indent level:



#@+node:ekr.20060824111500.87: *8* indentCloseBrackets and indentOpenBrackets
The line and the one before it are scanned for brackets listed in the
indentCloseBrackets and indentOpenBrackets properties. Opening brackets in the
previous line increase indent.

If lineUpClosingBracket is set to true, then closing brackets on the current
line will line up with the line containing the matching opening bracket. For
example, in Java mode lineUpClosingBracket is set to true, resulting in brackets
being indented like so:

{
    // Code
    {
        // More code
    }
}

If lineUpClosingBracket is set to false, the line after a closing bracket will
be lined up with the line containing the matching opening bracket. For example,
in Lisp mode lineUpClosingBracket is set to false, resulting in brackets being
indented like so:

(foo 'a-parameter
    (crazy-p)
    (bar baz ()))
(print "hello world")
#@+node:ekr.20060824111500.88: *8* doubleBracketIndent
If the previous line contains no opening brackets, or if the
doubleBracketIndent property is set to true, the previous line is checked
against the regular expressions in the indentNextLine and indentNextLines
properties. If the previous line matches the former, the indent of the current
line is increased and the subsequent line is shifted back again. If the previous
line matches the latter, the indent of the current and subsequent lines is
increased.

In Java mode, for example, the indentNextLine property is set to match control
structures such as “if”, “else”, “while”, and so on.

The doubleBracketIndent property, if set to the default of false, results in code indented like so::

    while(objects.hasNext())
    {
        Object next = objects.hasNext();
        if(next instanceof Paintable)
            next.paint(g);
    }

On the other hand, settings this property to “true” will give the following result::

    while(objects.hasNext())
        {
            Object next = objects.hasNext();
            if(next instanceof Paintable)
                next.paint(g);
        }
#@+node:ekr.20060824111500.89: *7* Buffer-Local Properties
Buffer-local properties provide an alternate way to change editor settings on a
per-buffer basis. While changes made in the Buffer Options dialog box are lost
after the buffer is closed, buffer-local properties take effect each time the
file is opened, because they are embedded in the file itself. 

When jEdit loads a file, it checks the first and last 10 lines for
colon-enclosed name/value pairs. For example, placing the following in a buffer
changes the indent width to 4 characters, enables soft tabs, and activates the
Perl edit mode: 

:indentSize=4:noTabs=true:mode=perl:

Adding buffer-local properties to a buffer takes effect after the next time the
buffer is saved. 
#@+node:ekr.20060824111500.90: *8* collapseFolds
Folds with a level of this or higher will be collapsed when the buffer is
opened. If set to zero, all folds will be expanded initially. See the section
called “Folding”.

#@+node:ekr.20060824111500.91: *8* deepIndent
When set to “true”, multiple-line expressions delimited by parentheses are aligned like so::

    retVal.x = (int)(horizontalOffset
        + Chunk.offsetToX(info.chunks,
                          offset));

With this setting disabled, the text would look like so::

    retVal.x = (int)(horizontalOffset
        + Chunk.offsetToX(info.chunks,
        offset));
#@+node:ekr.20060824111500.92: *8* folding
The fold mode; one of “none”, “indent”, “explicit”, or the name of a plugin
folding mode. See the section called “Folding”.
#@+node:ekr.20060824111500.93: *8* indentSize
The width, in characters, of one indent. Must be an integer greater than 0. See
the section called “Tabbing and Indentation”.
#@+node:ekr.20060824111500.94: *8* maxLineLen
The maximum line length and wrap column position. Inserting text beyond this
column will automatically insert a line break at the appropriate position. See
the section called “Inserting and Deleting Text”.

#@+node:ekr.20060824111500.95: *8* mode
The default edit mode for the buffer. See the section called “Edit Modes”. 

#@+node:ekr.20060824111500.96: *8* noTabs
If set to “true”, soft tabs (multiple space characters) will be used instead of
“real” tabs. See the section called “Tabbing and Indentation”.

#@+node:ekr.20060824111500.97: *8* noWordSep
A list of non-alphanumeric characters that are not to be treated as word
separators. Global default is “_”. tabSize The tab width. Must be an integer
greater than 0. See the section called “Tabbing and Indentation”.

#@+node:ekr.20060824111500.98: *8* wordBreakChars
Characters, in addition to spaces and tabs, at which lines may be split when
word wrapping. See the section called “Inserting and Deleting Text”.

#@+node:ekr.20060824111500.99: *8* wrap
The word wrap mode; one of “none”, “soft”, or “hard”. See the section called
“Wrapping Long Lines”.
#@+node:ekr.20060824111500.100: *6* rules
For information about using more than one ruleset, see the section called “The SPAN Tag”.

Attributes: SET, IGNORE_CASE, NO_WORD_SEP, DEFAULT, HIGHLIGHT_DIGITS DIGIT_RE

<RULES IGNORE_CASE="FALSE" HIGHLIGHT_DIGITS="TRUE">
    ... parser rules go here ...
</RULES>
#@+node:ekr.20060824111500.67: *7* For rules only
#@+node:ekr.20060824111500.68: *8* DEFAULT (token type)
The token type for text which doesn't match any specific rule. Default is NULL.
See the section called “Token Types” for a list of token types.

For 'rules' only.
#@+node:ekr.20060824111500.69: *8* HIGHLIGHT_DIGITS  &DIGITS_RE (bool)
If the HIGHLIGHT_DIGITS attribute is set to TRUE, jEdit will attempt to highlight numbers in this ruleset.

Any word consisting entirely of digits (0-9) will be highlighted with the DIGIT token type.

A word that contains other letters in addition to digits will be highlighted with the DIGIT token type only if it matches the regular expression specified in the DIGIT_RE attribute. If this attribute is not specified, it will not be highlighted.

For 'rules' only.

Example: a DIGIT_RE regular expression that highlights Java-style
numeric literals (normal numbers, hexadecimals prefixed with 0x, numbers
suffixed with various type indicators, and floating point literals containing an
exponent):

DIGIT_RE="(0x[[:xdigit:]]+|[[:digit:]]+(e[[:digit:]]*)?)[lLdDfF]?"
#@+node:ekr.20060824111500.70: *8* IGNORE_CASE (bool)
If set to FALSE, matches will be case sensitive.
Otherwise, case will not matter. Default is TRUE.

For 'rules' only.
#@+node:ekr.20060824111500.71: *8* SET (string)
The name of this ruleset. All rulesets other than the first must have a name.

For 'rules' only.
#@+node:ekr.20060824111500.72: *8* NO_WORD_SEP
Any non-alphanumeric character not in this list is treated as a word separator
for the purposes of syntax highlighting.

For 'rules' only.
#@+node:ekr.20060824111500.101: *6* seq (can use DELEGATE)
The SEQ rule highlights fixed sequences of text. The text to highlight is
specified between opening and closing SEQ tags. The following attributes are
supported:

Attributes: TYPE, AT_xxx, DELEGATE

Examples:

<SEQ TYPE="OPERATOR">+</SEQ>
<SEQ TYPE="OPERATOR">-</SEQ>
<SEQ TYPE="OPERATOR">*</SEQ>
<SEQ TYPE="OPERATOR">/</SEQ>
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.102: *6* seq_regexp (can use DELEGATE)
The SEQ_REGEXP rule is similar to the SEQ rule except the match sequence is
taken to be a regular expression.

Attributes: TYPE, AT_xxx, DELEGATE, HASH_CHAR(required)

Example: a SEQ_REGEXP rule that highlights Perl's matcher
constructions such as m/(.+):(\d+):(.+)/:

<SEQ_REGEXP TYPE="MARKUP"
    HASH_CHAR="m"
    AT_WORD_START="TRUE"
>m([[:punct:]])(?:.*?[^\\])*?\1[sgiexom]*</SEQ_REGEXP>
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.103: *6* span  (can use DELEGATE)
The SPAN rule highlights text between a start and end string. The start and end
strings are specified inside child elements of the SPAN tag.

Attributes: TYPE, AT_xxx, DELEGATE, EXCLUDE_MATCH, NO_xxx,

Example: a SPAN that highlights Java string literals, which cannot include line breaks:

<SPAN TYPE="LITERAL1" NO_LINE_BREAK="TRUE">
  <BEGIN>"</BEGIN>
  <END>"</END>
</SPAN>

Example: a SPAN that highlights Java documentation comments by delegating to the
“JAVADOC” ruleset defined elsewhere in the current mode:

<SPAN TYPE="COMMENT2" DELEGATE="JAVADOC">
  <BEGIN>/**</BEGIN>
  <END>*/</END>
</SPAN>

Example: a SPAN that highlights HTML cascading stylesheets inside <STYLE> tags by
delegating to the main ruleset in the CSS edit mode:

<SPAN TYPE="MARKUP" DELEGATE="css::MAIN">
  <BEGIN>&lt;style&gt;</BEGIN>
  <END>&lt;/style&gt;</END>
</SPAN>
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.24: *7* EXCLUDE_MATCH (bool)
If set to TRUE, the match will not be highlighted, only the text before it will.
#@+node:ekr.20060824111500.25: *7* NO_xxx
#@+node:ekr.20060824111500.26: *8* NO_WORD_BREAK (bool)
If set to TRUE, the span will not cross word breaks.

For 'span' only.
#@+node:ekr.20060824111500.27: *8* NO_LINE_BREAK (bool)
If set to TRUE, the span will not cross line breaks.

For 'span' only.
#@+node:ekr.20060824111500.28: *8* NO_ESCAPE (bool)
If set to TRUE, the ruleset's escape character will have no effect before the
span's end string. Otherwise, the presence of the escape character will cause
that occurrence of the end string to be ignored.

For 'span' only.
#@+node:ekr.20060824111500.104: *6* span_regexp  (can use DELEGATE)
The SPAN_REGEXP rule is similar to the SPAN rule except the start sequence is
a regular expression.

Attributes: TYPE, AT_xxx, DELEGATE, EXCLUDE_MATCH, NO_xxx, HASH_CHAR (required).

Any text matched by groups in the BEGIN regular expression is substituted in the
END string. See below for an example of where this is useful. 

Example: a SPAN_REGEXP rule that highlights “read-ins” in shell scripts:

<SPAN_REGEXP HASH_CHAR="<" TYPE="LITERAL1" DELEGATE="LITERAL">
    <BEGIN><![CDATA[<<[[:space:]'"]*([[:alnum:]_]+)[[:space:]'"]*]]></BEGIN>
    <END>$1</END>
</SPAN_REGEXP>

Example: a SPAN_REGEXP rule that highlights constructs placed between <#ftl and
>, as long as the <#ftl is followed by a word break:

<SPAN_REGEXP TYPE="KEYWORD1" HASH_CHAR="&lt;" DELEGATE="EXPRESSION">
    <BEGIN>&lt;#ftl\&gt;</BEGIN>
    <END>&gt;</END>
</SPAN_REGEXP>
#@+node:ekr.20060824111500.19: *7* AT_xxx
#@+node:ekr.20060824111500.20: *8* AT_LINE_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.21: *8* AT_WHITESPACE_END (bool)
If set to TRUE, the sequence will only be highlighted if it is the first non-whitespace text in the line.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.22: *8* AT_WORD_START (bool)
If set to TRUE, the sequence will only be highlighted if it occurs at the beginning of a word.

For 'seq', 'span', 'begin', 'end'
#@+node:ekr.20060824111500.35: *7* DELEGATE
EKR: This attribute is used in two completely different ways:

1.  In spans:

Text inside the span will be highlighted with the specified ruleset.

EKR: this is essentially a 'recursive' coloring. The delegate ruleset is the
only ruleset used.

2.  In import rules:

The imported ruleset is copied to the **end** of the containing ruleset,
**not** to the location of the IMPORT rule. This has implications with
rule-ordering; see the section called “Rule Ordering Requirements”.

To delegate to a ruleset defined in the current mode, just specify its name. To
delegate to a ruleset defined in another mode, specify a name of the form
mode::ruleset. Note that the first (unnamed) ruleset in a mode is called
“MAIN”.

#@+node:ekr.20060824111500.24: *7* EXCLUDE_MATCH (bool)
If set to TRUE, the match will not be highlighted, only the text before it will.
#@+node:ekr.20060824111500.25: *7* NO_xxx
#@+node:ekr.20060824111500.26: *8* NO_WORD_BREAK (bool)
If set to TRUE, the span will not cross word breaks.

For 'span' only.
#@+node:ekr.20060824111500.27: *8* NO_LINE_BREAK (bool)
If set to TRUE, the span will not cross line breaks.

For 'span' only.
#@+node:ekr.20060824111500.28: *8* NO_ESCAPE (bool)
If set to TRUE, the ruleset's escape character will have no effect before the
span's end string. Otherwise, the presence of the escape character will cause
that occurrence of the end string to be ignored.

For 'span' only.
#@+node:ekr.20060824111500.73: *7* HASH_CHAR (char)
The first character that the regular expression matches.

This rules out using regular expressions which can match more than one character
at the start position. The regular expression match cannot span more than one
line, either.

Required for eol_span_regexp, span_regexp, seq_regexp.
#@+node:ekr.20060824111500.105: *6* terminate
The TERMINATE rule specifies that parsing should stop after the specified number
of characters have been read from a line.

The number of characters to terminate after should be specified with the AT_CHAR
attribute. Here is an example:

<TERMINATE AT_CHAR="1" />

This rule is used in Patch mode, for example, because only the first character
of each line affects highlighting.
#@+node:ekr.20060824111500.66: *7* AT_CHAR (int)
The number of characters to terminate after.

For terminate only.
#@-all
#@@language python
#@@tabwidth -4

#@-leo
