#@+leo-ver=4-thin
#@+node:EKR.20040429143933:@thin leoProjects.txt
#@+at 
#@nonl
# This part of the tree shows views of the outline related to specific 
# projects or tasks.  I put such headlines in parentheses, and that is just my 
# convention.
# 
# I create a new view by cloning headlines that relate to its task, and moving 
# the cloned headlines under the task headline.  This greatly increases my 
# focus.  Any changes made in a task view to clone headlines affect the other 
# clones scattered throughout the outline.  In particular, all @file nodes 
# containing changed clones become marked as dirty, so they will be written 
# when the entire outline is saved.
#@-at
#@@c

#@@language python 
#@@tabwidth -4

#@+all
#@+node:ekr.20031218072017.1319:(Key handlers)
@ These routines are involved in many projects.  Cloning them over and over can slow down Leo a lot.
#@+node:ekr.20031218072017.1320:body key handlers
@ The <Key> event generates the event before the body text is changed(!), so we register an idle-event handler to do the work later.

1/17/02: Rather than trying to figure out whether the control or alt keys are down, we always schedule the idle_handler.  The idle_handler sees if any change has, in fact, been made to the body text, and sets the changed and dirty bits only if so.  This is the clean and safe way.

2/19/02: We must distinguish between commands like "Find, Then Change", that call onBodyChanged, and commands like "Cut" and "Paste" that call onBodyWillChange.  The former commands have already changed the body text, and that change must be captured immediately.  The latter commands have not changed the body text, and that change may only be captured at idle time.
@c

@others
#@nonl
#@+node:ekr.20031218072017.1321:idle_body_key
def idle_body_key (self,p,oldSel,undoType,ch=None,oldYview=None,newSel=None,oldText=None):
    
    """Update the body pane at idle time."""

    # g.trace(ch,ord(ch))
    c = self.c
    if not c: return "break"
    if not p: return "break"
    if not c.isCurrentPosition(p): return "break"

    if g.doHook("bodykey1",c=c,v=p,ch=ch,oldSel=oldSel,undoType=undoType):
        return "break" # The hook claims to have handled the event.
    body = p.bodyString()
    if not newSel:
        newSel = c.frame.body.getTextSelection()
    if oldText != None:
        s = oldText
    else:
        s = c.frame.body.getAllText()
    << return if nothing has changed >>
    << set removeTrailing >>
    if ch in ('\t','\n','\r',chr(8)):
        d = g.scanDirectives(c,p) # Support @tab_width directive properly.
        tab_width = d.get("tabwidth",c.tab_width) # ; g.trace(tab_width)
        if ch in ('\n','\r'):
            << Do auto indent >>
        elif ch == '\t' and tab_width < 0:
            << convert tab to blanks >>
        elif ch in (chr(8)) and tab_width < 0:
            << handle backspace with negative tab_width >>
    << set s to widget text, removing trailing newlines if necessary >>
    if undoType: # 11/6/03: set oldText properly when oldText param exists.
        if not oldText: oldText = body
        newText = s
        c.undoer.setUndoTypingParams(p,undoType,oldText,newText,oldSel,newSel,oldYview=oldYview)
    p.v.setTnodeText(s)
    p.v.t.insertSpot = c.frame.body.getInsertionPoint()
    << recolor the body >>
    if not c.changed:
        c.setChanged(True)
    << redraw the screen if necessary >>
    g.doHook("bodykey2",c=c,v=p,ch=ch,oldSel=oldSel,undoType=undoType)
    return "break"
#@+node:ekr.20031218072017.1322:<< return if nothing has changed >>
# 6/22/03: Make sure we handle delete key properly.
if ch not in ('\n','\r',chr(8)):

    if s == body:
        return "break"

    # Do nothing for control characters.
    if (ch == None or len(ch) == 0) and body == s[:-1]:
        return "break"
#@nonl
#@-node:ekr.20031218072017.1322:<< return if nothing has changed >>
#@+node:ekr.20031218072017.1323:<< set removeTrailing >>
@ Tk will add a newline only if:
1. A real change has been made to the Tk.Text widget, and
2. the change did _not_ result in the widget already containing a newline.

It's not possible to tell, given the information available, what Tk has actually done. We need only make a reasonable guess here.   setUndoTypingParams stores the number of trailing newlines in each undo bead, so whatever we do here can be faithfully undone and redone.
@c
new = s ; old = body

if len(new) == 0 or new[-1] != '\n':
    # There is no newline to remove.  Probably will never happen.
    removeTrailing = False
elif len(old) == 0:
    # Ambigous case.  Formerly always returned False.
    if new == "\n\n":
        removeTrailing = True # Handle a very strange special case.
    else:
        removeTrailing = ch not in ('\r','\n')
elif old == new[:-1]:
    # A single trailing character has been added.
    removeTrailing = ch not in ('\r','\n') # 6/12/04: Was false.
else:
    # The text didn't have a newline, and now it does.
    # Moveover, some other change has been made to the text,
    # So at worst we have misrepresented the user's intentions slightly.
    removeTrailing = True

if 0:
    print removeTrailing
    print repr(ch)
    print repr(oldText)
    print repr(old)
    print repr(new)
#@nonl
#@-node:ekr.20031218072017.1323:<< set removeTrailing >>
#@+node:ekr.20031218072017.1324:<< Do auto indent >> (David McNab)
# Do nothing if we are in @nocolor mode or if we are executing a Change command.
if self.frame.body.colorizer.useSyntaxColoring(p) and undoType != "Change":
    # Get the previous line.
    s=c.frame.bodyCtrl.get("insert linestart - 1 lines","insert linestart -1c")
    # Add the leading whitespace to the present line.
    junk,width = g.skip_leading_ws_with_indent(s,0,tab_width)
    if s and len(s) > 0 and s[-1]==':':
        # For Python: increase auto-indent after colons.
        if self.colorizer.scanColorDirectives(p) == "python":
            width += abs(tab_width)
    if g.app.config.getBoolWindowPref("smart_auto_indent"):
        # Added Nov 18 by David McNab, david@rebirthing.co.nz
        # Determine if prev line has unclosed parens/brackets/braces
        brackets = [width]
        tabex = 0
        for i in range(0, len(s)):
            if s[i] == '\t':
                tabex += tab_width - 1
            if s[i] in '([{':
                brackets.append(i+tabex + 1)
            elif s[i] in '}])' and len(brackets) > 1:
                brackets.pop()
        width = brackets.pop()
        # end patch by David McNab
    ws = g.computeLeadingWhitespace (width,tab_width)
    if ws and len(ws) > 0:
        c.frame.bodyCtrl.insert("insert", ws)
        removeTrailing = False # bug fix: 11/18
#@nonl
#@-node:ekr.20031218072017.1324:<< Do auto indent >> (David McNab)
#@+node:ekr.20031218072017.1325:<< convert tab to blanks >>
# Do nothing if we are executing a Change command.
if undoType != "Change":
    
    # Get the characters preceeding the tab.
    prev=c.frame.bodyCtrl.get("insert linestart","insert -1c")
    
    if 1: # 6/26/03: Convert tab no matter where it is.

        w = g.computeWidth(prev,tab_width)
        w2 = (abs(tab_width) - (w % abs(tab_width)))
        # g.trace("prev w:",w,"prev chars:",prev)
        c.frame.bodyCtrl.delete("insert -1c")
        c.frame.bodyCtrl.insert("insert",' ' * w2)
    
    else: # Convert only leading tabs.
    
        # Get the characters preceeding the tab.
        prev=c.frame.bodyCtrl.get("insert linestart","insert -1c")

        # Do nothing if there are non-whitespace in prev:
        all_ws = True
        for ch in prev:
            if ch != ' ' and ch != '\t':
                all_ws = False
        if all_ws:
            w = g.computeWidth(prev,tab_width)
            w2 = (abs(tab_width) - (w % abs(tab_width)))
            # g.trace("prev w:",w,"prev chars:",prev)
            c.frame.bodyCtrl.delete("insert -1c")
            c.frame.bodyCtrl.insert("insert",' ' * w2)
#@nonl
#@-node:ekr.20031218072017.1325:<< convert tab to blanks >>
#@+node:EKR.20040604090913:<< handle backspace with negative tab_width >>
# Get the preceeding characters.
prev   =c.frame.bodyCtrl.get("insert linestart","insert")
allPrev=c.frame.bodyCtrl.get("1.0","insert")
n = len(allPrev)
try:
    oldAllPrev = body[:n]
    assert(allPrev==oldAllPrev)
    deletedChar = body[n:n+1]
except (IndexError,AssertionError):
    deletedChar = None

if deletedChar in (u' ',' '):
    n = len(prev) ; w = abs(tab_width)
    n2 = n % w # Delete up to n2 - 1 spaces.
    if n2 == w - 1: # Delete spaces only if they could have come from a tab.
        count = 0
        while n2 > 0:
            n2 -= 1
            ch = prev[n-count-1]
            # g.trace(count,repr(ch))
            if ch in (u' ',' '): count += 1
            else: break
        # g.trace(count,(n%w))
        if count > 0:
            c.frame.bodyCtrl.delete("insert -%dc" % count,"insert")
#@nonl
#@-node:EKR.20040604090913:<< handle backspace with negative tab_width >>
#@+node:ekr.20031218072017.1326:<< set s to widget text, removing trailing newlines if necessary >>
s = c.frame.body.getAllText()
if len(s) > 0 and s[-1] == '\n' and removeTrailing:
    s = s[:-1]
    
# Major change: 6/12/04
if s == body:
    # print "no real change"
    return "break"
#@nonl
#@-node:ekr.20031218072017.1326:<< set s to widget text, removing trailing newlines if necessary >>
#@+node:ekr.20031218072017.1327:<< recolor the body >>
self.frame.scanForTabWidth(p)

incremental = undoType not in ("Cut","Paste") and not self.forceFullRecolorFlag
self.frame.body.recolor_now(p,incremental=incremental)

self.forceFullRecolorFlag = False
#@nonl
#@-node:ekr.20031218072017.1327:<< recolor the body >>
#@+node:ekr.20031218072017.1328:<< redraw the screen if necessary >>
redraw_flag = False

c.beginUpdate()

# Update dirty bits.
if not p.isDirty() and p.setDirty(): # Sets all cloned and @file dirty bits
    redraw_flag = True
    
# Update icons.
val = p.computeIcon()

# 7/8/04: During unit tests the node may not have been drawn,
# So p.v.iconVal may not exist yet.
if not hasattr(p.v,"iconVal") or val != p.v.iconVal:
    p.v.iconVal = val
    redraw_flag = True

c.endUpdate(redraw_flag) # redraw only if necessary
#@nonl
#@-node:ekr.20031218072017.1328:<< redraw the screen if necessary >>
#@-node:ekr.20031218072017.1321:idle_body_key
#@+node:ekr.20031218072017.1329:onBodyChanged (called from core)
# Called by command handlers that have already changed the text.

def onBodyChanged (self,p,undoType,oldSel=None,oldYview=None,newSel=None,oldText=None):
    
    """Handle a change to the body pane."""
    
    c = self.c
    if not p:
        p = c.currentPosition()

    if not oldSel:
        oldSel = c.frame.body.getTextSelection()

    self.idle_body_key(p,oldSel,undoType,oldYview=oldYview,newSel=newSel,oldText=oldText)
#@nonl
#@-node:ekr.20031218072017.1329:onBodyChanged (called from core)
#@+node:ekr.20031218072017.1330:onBodyKey
def onBodyKey (self,event):
    
    """Handle any key press event in the body pane."""

    c = self.c ; ch = event.char
    
    # This translation is needed on MacOS.
    if ch == '':
        d = {'Return':'\r', 'Tab':'\t', 'BackSpace':chr(8)}
        ch = d.get(event.keysym,'')

    oldSel = c.frame.body.getTextSelection()
    
    p = c.currentPosition()

    if 0: # won't work when menu keys are bound.
        self.handleStatusLineKey(event)
        
    # We must execute this even if len(ch) > 0 to delete spurious trailing newlines.
    self.c.frame.bodyCtrl.after_idle(self.idle_body_key,p,oldSel,"Typing",ch)
#@nonl
#@+node:ekr.20040105223536:handleStatusLineKey
def handleStatusLineKey (self,event):
    
    c = self.c ; frame = c.frame
    ch = event.char ; keysym = event.keysym
    keycode = event.keycode ; state = event.state

    if 1: # ch and len(ch)>0:
        << trace the key event >>

    try:
        status = self.keyStatus
    except:
        status = [] ; frame.clearStatusLine()
    
    for sym,name in (
        ("Alt_L","Alt"),("Alt_R","Alt"),
        ("Control_L","Control"),("Control_R","Control"),
        ("Escape","Esc"),
        ("Shift_L","Shift"), ("Shift_R","Shift")):
        if keysym == sym:
            if name not in status:
                status.append(name)
                frame.putStatusLine(name + ' ')
            break
    else:
        status = [] ; frame.clearStatusLine()

    self.keyStatus = status
#@nonl
#@+node:ekr.20040105223536.1:<< trace the key event >>
try:    self.keyCount += 1
except: self.keyCount  = 1

printable = g.choose(ch == keysym and state < 4,"printable","")

print "%4d %s %d %s %x %s" % (
    self.keyCount,repr(ch),keycode,keysym,state,printable)
#@nonl
#@-node:ekr.20040105223536.1:<< trace the key event >>
#@-node:ekr.20040105223536:handleStatusLineKey
#@-node:ekr.20031218072017.1330:onBodyKey
#@+node:ekr.20031218072017.1331:onBodyWillChange
# Called by command handlers that change the text just before idle time.

def onBodyWillChange (self,p,undoType,oldSel=None,oldYview=None):
    
    """Queue the body changed idle handler."""
    
    c = self.c

    if not oldSel:
        oldSel = c.frame.body.getTextSelection()

    if not p:
        p = c.currentPosition()

    self.c.frame.bodyCtrl.after_idle(self.idle_body_key,p,oldSel,undoType,oldYview)
#@nonl
#@-node:ekr.20031218072017.1331:onBodyWillChange
#@-node:ekr.20031218072017.1320:body key handlers
#@+node:ekr.20040803072955.91:idle_head_key
def idle_head_key (self,p,ch=None):
    
    """Update headline text at idle time."""
    
    c = self.c

    if not p or not p.isCurrentPosition():
        return "break"
        
    edit_text = self.edit_text(p)
    index = edit_text.index("insert")

    if g.doHook("headkey1",c=c,p=p,ch=ch):
        return "break" # The hook claims to have handled the event.
        
    << set head to vnode text >>
    done = ch in ('\r','\n')
    if done:
        << set the widget text to head >>
    << set s to the widget text >>
    changed = s != head
    if changed:
        c.undoer.setUndoParams("Change Headline",p,newText=s,oldText=head)
        << update p >>
    if done or changed:
        << reconfigure p and all nodes joined to p >>
        << update the screen >>

    g.doHook("headkey2",c=c,p=p,ch=ch)
    return "break"
#@nonl
#@+node:ekr.20040803072955.92:<< set head to vnode text >>
head = p.headString()
if head == None:
    head = u""
head = g.toUnicode(head,"utf-8")
#@nonl
#@-node:ekr.20040803072955.92:<< set head to vnode text >>
#@+node:ekr.20040803072955.93:<< set the widget text to head >>
self.setText(edit_text,head,tag="idle_head_key")
edit_text.mark_set("insert",index)
#@nonl
#@-node:ekr.20040803072955.93:<< set the widget text to head >>
#@+node:ekr.20040803072955.94:<< set s to the widget text >>
s = edit_text.get("1.0","end")

# Don't truncate if the user is hitting return.
# That should just end editing.
if 1:
    # Truncate headline text to workaround Tk problems...
    # Another kludge: remove one or two trailing newlines before warning of truncation.
    if s and s[-1] == '\n': s = s[:-1]
    if s and s[-1] == '\n': s = s[:-1]
    i = s.find('\n')
    if i > -1:
        # g.trace(i,len(s),repr(s))
        g.es("Truncating headline to one line",color="blue")
        s = s[:i]
    limit = 1000
    if len(s) > limit:
        g.es("Truncating headline to %d characters" % (limit),color="blue")
        s = s[:limit]

s = g.toUnicode(s,g.app.tkEncoding)

if not s:
    s = u""
    
if 0: # 6/10/04: No longer needed.  This was stressing Tk needlessly.
    s = s.replace('\n','')
    s = s.replace('\r','')
#@nonl
#@-node:ekr.20040803072955.94:<< set s to the widget text >>
#@+node:ekr.20040803072955.95:<< update p >>
c.beginUpdate()
if 1: # update...
    # Update changed bit.
    if not c.changed:
        c.setChanged(True)
    # Update all dirty bits.
    # Bug fix 8/2/04: must call p.setDirty even if p is dirty!
    p.setDirty()
    # Update p.
    p.initHeadString(s)
    self.setText(edit_text,s,tag="idle_head_key2")
    edit_text.mark_set("insert",index)
c.endUpdate(False) # do not redraw now.
#@nonl
#@-node:ekr.20040803072955.95:<< update p >>
#@+node:ekr.20040803072955.96:<< reconfigure p and all nodes joined to p >>
# Reconfigure p's headline.
if done:
    self.setDisabledLabelState(p)

edit_text.configure(width=self.headWidth(p))
#@nonl
#@-node:ekr.20040803072955.96:<< reconfigure p and all nodes joined to p >>
#@+node:ekr.20040803072955.97:<< update the screen >>
if done:
    # g.trace("done")
    c.beginUpdate()
    self.endEditLabel()
    c.endUpdate()

elif changed:
    # g.trace("changed")
    # Update p immediately.  Joined nodes are redrawn later by endEditLabel.
    # Redrawing the whole screen now messes up the cursor in the headline.
    self.drawIcon(p) # just redraw the icon.
#@nonl
#@-node:ekr.20040803072955.97:<< update the screen >>
#@-node:ekr.20040803072955.91:idle_head_key
#@-node:ekr.20031218072017.1319:(Key handlers)
#@+node:ekr.20031218072017.1341:(scanAllDirectives, scanDirectives, related utils)
@ These routines are involved in many projects.  Cloning them over and over can slow down Leo a lot.
#@nonl
#@+node:ekr.20031218072017.2387:old_df.scanAllDirectives
@ Once a directive is seen, no other related directives in nodes further up the tree have any effect.  For example, if an @color directive is seen in node p, no @color or @nocolor directives are examined in any ancestor of p.

This code is similar to Commands.scanAllDirectives, but it has been modified for use by the atFile class.
@c

def scanAllDirectives(self,p,scripting=False,importing=False,reading=False):
    
    """Scan position p and p's ancestors looking for directives,
    setting corresponding atFile ivars.
    """

    c = self.c
    << Set ivars >>
    << Set path from @file node >>
    old = {}
    for p in p.self_and_parents_iter():
        s = p.v.t.bodyString
        dict = g.get_directives_dict(s)
        << Test for @path >>
        << Test for @encoding >>
        << Test for @comment and @language >>
        << Test for @header and @noheader >>
        << Test for @lineending >>
        << Test for @pagewidth >>
        << Test for @tabwidth >>
        old.update(dict)
    << Set current directory >>
    if not importing and not reading:
        # 5/19/04: don't override comment delims when reading!
        << Set comment strings from delims >>
#@nonl
#@+node:ekr.20031218072017.2388:<< Set ivars >>
self.page_width = self.c.page_width
self.tab_width  = self.c.tab_width

self.default_directory = None # 8/2: will be set later.

delim1, delim2, delim3 = g.set_delims_from_language(c.target_language)
self.language = c.target_language

self.encoding = g.app.config.default_derived_file_encoding
self.output_newline = g.getOutputNewline() # 4/24/03: initialize from config settings.
#@nonl
#@-node:ekr.20031218072017.2388:<< Set ivars >>
#@+node:ekr.20031218072017.2389:<< Set path from @file node >> in scanDirectory in leoGlobals.py
# An absolute path in an @file node over-rides everything else.
# A relative path gets appended to the relative path by the open logic.

name = p.anyAtFileNodeName() # 4/28/04

dir = g.choose(name,g.os_path_dirname(name),None)

if dir and len(dir) > 0 and g.os_path_isabs(dir):
    if g.os_path_exists(dir):
        self.default_directory = dir
    else: # 9/25/02
        self.default_directory = g.makeAllNonExistentDirectories(dir)
        if not self.default_directory:
            self.error("Directory \"" + dir + "\" does not exist")
#@nonl
#@-node:ekr.20031218072017.2389:<< Set path from @file node >> in scanDirectory in leoGlobals.py
#@+node:ekr.20031218072017.2390:<< Test for @comment and @language >>
# 10/17/02: @language and @comment may coexist in @file trees.
# For this to be effective the @comment directive should follow the @language directive.

if not old.has_key("comment") and dict.has_key("comment"):
    k = dict["comment"]
    # 11/14/02: Similar to fix below.
    delim1, delim2, delim3 = g.set_delims_from_string(s[k:])

# Reversion fix: 12/06/02: We must use elif here, not if.
elif not old.has_key("language") and dict.has_key("language"):
    k = dict["language"]
    # 11/14/02: Fix bug reported by J.M.Gilligan.
    self.language,delim1,delim2,delim3 = g.set_language(s,k)
#@nonl
#@-node:ekr.20031218072017.2390:<< Test for @comment and @language >>
#@+node:ekr.20031218072017.2391:<< Test for @encoding >>
if not old.has_key("encoding") and dict.has_key("encoding"):
    
    e = g.scanAtEncodingDirective(s,dict)
    if e:
        self.encoding = e
#@nonl
#@-node:ekr.20031218072017.2391:<< Test for @encoding >>
#@+node:ekr.20031218072017.2392:<< Test for @header and @noheader >>
# EKR: 10/10/02: perform the sames checks done by tangle.scanAllDirectives.
if dict.has_key("header") and dict.has_key("noheader"):
    g.es("conflicting @header and @noheader directives")
#@nonl
#@-node:ekr.20031218072017.2392:<< Test for @header and @noheader >>
#@+node:ekr.20031218072017.2393:<< Test for @lineending >>
if not old.has_key("lineending") and dict.has_key("lineending"):
    
    lineending = g.scanAtLineendingDirective(s,dict)
    if lineending:
        self.output_newline = lineending
#@-node:ekr.20031218072017.2393:<< Test for @lineending >>
#@+node:ekr.20031218072017.2394:<< Test for @path >>
# We set the current director to a path so future writes will go to that directory.

if not self.default_directory and not old.has_key("path") and dict.has_key("path"):

    k = dict["path"]
    << compute relative path from s[k:] >>
    if path and len(path) > 0:
        base = g.getBaseDirectory() # returns "" on error.
        path = g.os_path_join(base,path)
        if g.os_path_isabs(path):
            << handle absolute path >>
        else:
            self.error("ignoring bad @path: " + path)
    else:
        self.error("ignoring empty @path")
#@nonl
#@+node:ekr.20031218072017.2395:<< compute relative path from s[k:] >>
j = i = k + len("@path")
i = g.skip_to_end_of_line(s,i)
path = string.strip(s[j:i])

# Remove leading and trailing delims if they exist.
if len(path) > 2 and (
    (path[0]=='<' and path[-1] == '>') or
    (path[0]=='"' and path[-1] == '"') ):
    path = path[1:-1]
path = path.strip()

if 0: # 11/14/02: we want a _relative_ path, not an absolute path.
    path = g.os_path_join(g.app.loadDir,path)
#@nonl
#@-node:ekr.20031218072017.2395:<< compute relative path from s[k:] >>
#@+node:ekr.20031218072017.2396:<< handle absolute path >>
# path is an absolute path.

if g.os_path_exists(path):
    self.default_directory = path
else: # 9/25/02
    self.default_directory = g.makeAllNonExistentDirectories(path)
    if not self.default_directory:
        self.error("invalid @path: " + path)
#@-node:ekr.20031218072017.2396:<< handle absolute path >>
#@-node:ekr.20031218072017.2394:<< Test for @path >>
#@+node:ekr.20031218072017.2397:<< Test for @pagewidth >>
if dict.has_key("pagewidth") and not old.has_key("pagewidth"):
    
    w = g.scanAtPagewidthDirective(s,dict,issue_error_flag=True)
    if w and w > 0:
        self.page_width = w
#@nonl
#@-node:ekr.20031218072017.2397:<< Test for @pagewidth >>
#@+node:ekr.20031218072017.2398:<< Test for @tabwidth >>
if dict.has_key("tabwidth") and not old.has_key("tabwidth"):
    
    w = g.scanAtTabwidthDirective(s,dict,issue_error_flag=True)
    if w and w != 0:
        self.tab_width = w

#@-node:ekr.20031218072017.2398:<< Test for @tabwidth >>
#@+node:ekr.20031218072017.2399:<< Set current directory >>
# This code is executed if no valid absolute path was specified in the @file node or in an @path directive.

if c.frame and not self.default_directory:
    base = g.getBaseDirectory() # returns "" on error.
    for dir in (c.tangle_directory,c.frame.openDirectory,c.openDirectory):
        if dir and len(dir) > 0:
            dir = g.os_path_join(base,dir)
            if g.os_path_isabs(dir): # Errors may result in relative or invalid path.
                if g.os_path_exists(dir):
                    self.default_directory = dir ; break
                else: # 9/25/02
                    self.default_directory = g.makeAllNonExistentDirectories(dir)

if not self.default_directory and not scripting and not importing:
    # This should never happen: c.openDirectory should be a good last resort.
    g.trace()
    self.error("No absolute directory specified anywhere.")
    self.default_directory = ""
#@-node:ekr.20031218072017.2399:<< Set current directory >>
#@+node:ekr.20031218072017.2400:<< Set comment strings from delims >>
if scripting:
    # Force Python language.
    delim1,delim2,delim3 = g.set_delims_from_language("python")
    self.language = "python"
    
# Use single-line comments if we have a choice.
# 8/2/01: delim1,delim2,delim3 now correspond to line,start,end

if delim1:
    self.startSentinelComment = delim1
    self.endSentinelComment = "" # Must not be None.
elif delim2 and delim3:
    self.startSentinelComment = delim2
    self.endSentinelComment = delim3
else: # Emergency!
    # assert(0)
    g.es("Unknown language: using Python comment delimiters")
    g.es("c.target_language:",c.target_language)
    g.es("delim1,delim2,delim3:",delim1,delim2,delim3)
    self.startSentinelComment = "#" # This should never happen!
    self.endSentinelComment = ""
    
# g.trace(repr(self.startSentinelComment),repr(self.endSentinelComment))
#@nonl
#@-node:ekr.20031218072017.2400:<< Set comment strings from delims >>
#@-node:ekr.20031218072017.2387:old_df.scanAllDirectives
#@+node:ekr.20031218072017.1356:tangle.init_ivars & init_directive_ivars
# Called by __init__

def init_ivars(self):

    c = self.c
    << init tangle ivars >>
    << init untangle ivars >>
    
# Called by scanAllDirectives

def init_directive_ivars (self):

    c = self.c
    << init directive ivars >>
#@nonl
#@+node:ekr.20031218072017.1357:<< init tangle ivars >>
# Various flags and counts...

self.errors = 0 # The number of errors seen.
self.tangling = True # True if tangling, False if untangling.
self.path_warning_given = False # True: suppress duplicate warnings.
self.tangle_indent = 0 # Level of indentation during pass 2, in spaces.
if c.frame:
    self.file_name = c.mFileName # The file name (was a bridge function)
else:
    self.file_name = "<unknown file name>"
self.p = None # position being processed.
self.output_file = None # The file descriptor of the output file.
self.start_mode = "doc" # "code" or "doc".  Use "doc" for compatibility.
self.tangle_default_directory = None # Default directory set by scanAllDirectives.

@ Symbol tables: the TST (Tangle Symbol Table) contains all section names in the outline. The UST (Untangle Symbol Table) contains all sections defined in the derived file.
@c
self.tst = {}
self.ust = {}

# The section stack for Tangle and the definition stack for Untangle.
self.section_stack = []
self.def_stack = []

@ The list of all roots. The symbol table routines add roots to self list during pass 1. Pass 2 uses self list to generate code for all roots.
@c
self.root_list = []

# The delimiters for comments created by the @comment directive.
self.single_comment_string = "//"  # present comment delimiters.
self.start_comment_string = "/*"
self.end_comment_string = "*/"
self.sentinel = None

# The filename following @root in a headline.
# The code that checks for < < * > > = uses these globals.
self.root = None
self.root_name = None

# Formerly the "tangle private globals"
# These save state during tangling and untangling.
# It is possible that these will be removed...
if 1:
    self.head_root = None
    self.code = None
    self.doc = None
    self.header_name = None
    self.header = None
    self.section_name = None

@ The following records whether we have seen an @code directive in a body text.
If so, an @code represents < < header name > > = and it is valid to continue a section definition.
@c
self.code_seen = False # True if @code seen in body text.

# Support of output_newline option
self.output_newline = g.getOutputNewline()
#@nonl
#@-node:ekr.20031218072017.1357:<< init tangle ivars >>
#@+node:ekr.20031218072017.1358:<< init untangle ivars >>
@ Untangle vars used while comparing.
@c
self.line_comment = self.comment = self.comment_end = None
self.comment2 = self.comment2_end = None
self.string1 = self.string2 = self.verbatim = None
self.message = None # forgiving compare message.
#@nonl
#@-node:ekr.20031218072017.1358:<< init untangle ivars >>
#@+node:ekr.20031218072017.1359:<< init directive ivars >> (tangle)
if 0: # not used in this version of Leo
    self.allow_rich_text = default_allow_rich_text
    self.extended_noweb_flag = default_extended_noweb_flag
    self.target_language = default_target_language # uses c.target_lanuage instead
    
# Global options
self.page_width = c.page_width
self.tab_width = c.tab_width
self.tangle_batch_flag = c.tangle_batch_flag
self.untangle_batch_flag = c.untangle_batch_flag

# Default tangle options.
self.tangle_directory = None # Initialized by scanAllDirectives
self.output_doc_flag = c.output_doc_flag
self.use_header_flag = c.use_header_flag

# Default tangle language
self.language = c.target_language
delim1,delim2,delim3 = g.set_delims_from_language(self.language)
# g.trace(delim1,delim2,delim3)

# 8/1/02: this now works as expected.
self.single_comment_string = delim1
self.start_comment_string = delim2
self.end_comment_string = delim3

# Abbreviations for self.language.
# Warning: these must also be initialized in tangle.scanAllDirectives.
if 1: # 10/30/02: Don't change the code, just ignore @language cweb.
    self.use_cweb_flag = False
    self.raw_cweb_flag = self.language == "cweb" # A new ivar.
else:
    self.use_cweb_flag = self.language == "cweb"
    self.raw_cweb_flag = False # was never used before.

self.use_noweb_flag = not self.use_cweb_flag

# Set only from directives.
self.print_mode = "verbose"

# Stephen P. Schaefer 9/13/2002
# support @first directive
self.first_lines = ""
self.encoding = g.app.config.default_derived_file_encoding # 2/21/03
self.output_newline = g.getOutputNewline() # 4/24/03: initialize from config settings.
#@nonl
#@-node:ekr.20031218072017.1359:<< init directive ivars >> (tangle)
#@-node:ekr.20031218072017.1356:tangle.init_ivars & init_directive_ivars
#@+node:ekr.20031218072017.1360:tangle.scanAllDirectives
@ Once a directive is seen, related directives in ancesors have no effect.  For example, if an @color directive is seen in node x, no @color or @nocolor directives are examined in any ancestor of x.
@c

def scanAllDirectives(self,p,require_path_flag,issue_error_flag):
    
    """Scan vnode p and p's ancestors looking for directives,
    setting corresponding tangle ivars and globals.
    """

    c = self.c
    # g.trace(p)
    old = {} ; print_mode_changed = False
    self.init_directive_ivars()
    if p:
        s = p.bodyString()
        << Collect @first attributes >>
    for p in p.self_and_parents_iter():
        s = p.bodyString()
        dict = g.get_directives_dict(s)
        # g.trace("dict:",dict,p)
        << Test for @comment and @language >>
        << Test for @encoding >>
        << Test for @lineending >>
        << Test for print modes directives >>
        << Test for @path >>
        << Test for @pagewidth >>
        << Test for @root >>
        << Test for @tabwidth >>
        << Test for @header and @noheader >>
        old.update(dict)
    << Set self.tangle_directory >>
#@nonl
#@+node:ekr.20031218072017.1361:<< Collect @first attributes >>
@ Stephen P. Schaefer 9/13/2002: Add support for @first.
Unlike other root attributes, does *NOT* inherit from parent nodes.
@c
tag = "@first"
sizeString = len(s) # DTHEIN 13-OCT-2002: use to detect end-of-string
i = 0
while 1:
    # DTHEIN 13-OCT-2002: directives must start at beginning of a line
    if not g.match_word(s,i,tag):
        i = g.skip_line(s,i)
    else:
        i = i + len(tag)
        j = i = g.skip_ws(s,i)
        i = g.skip_to_end_of_line(s,i)
        if i>j:
            self.first_lines += s[j:i] + '\n'
        i = g.skip_nl(s,i)
    if i >= sizeString:  # DTHEIN 13-OCT-2002: get out when end of string reached
        break

#@-node:ekr.20031218072017.1361:<< Collect @first attributes >>
#@+node:ekr.20031218072017.1362:<< Test for @comment and @language >>
if old.has_key("comment") or old.has_key("language"):
     pass # Do nothing more.

elif dict.has_key("comment"):

    i = dict["comment"]
    delim1,delim2,delim3 = g.set_delims_from_string(s[i:])
    if delim1 or delim2:
        self.single_comment_string = delim1
        self.start_comment_string = delim2
        self.end_comment_string = delim3
        # @comment effectively disables Untangle.
        self.language = "unknown"
    else:
        if issue_error_flag:
            g.es("ignoring: " + s[i:])

elif dict.has_key("language"):

    i = dict["language"]
    language,delim1,delim2,delim3 = g.set_language(s,i)
    self.language = language
    self.single_comment_string = delim1
    self.start_comment_string = delim2
    self.end_comment_string = delim3
    if 0:
        g.trace(self.single_comment_string,
            self.start_comment_string,
            self.end_comment_string)

    # 10/30/02: These ivars must be updated here!
    # g.trace(self.language)
    self.use_noweb_flag = True
    self.use_cweb_flag = False # Only raw cweb mode is ever used.
    self.raw_cweb_flag = self.language == "cweb" # A new ivar.
#@-node:ekr.20031218072017.1362:<< Test for @comment and @language >>
#@+node:ekr.20031218072017.1363:<< Test for @encoding >>
if not old.has_key("encoding") and dict.has_key("encoding"):
    
    e = g.scanAtEncodingDirective(s,dict)
    if e:
        self.encoding = e
#@-node:ekr.20031218072017.1363:<< Test for @encoding >>
#@+node:ekr.20031218072017.1364:<< Test for @lineending >>
if not old.has_key("lineending") and dict.has_key("lineending"):
    
    lineending = g.scanAtLineendingDirective(s,dict)
    if lineending:
        self.output_newline = lineending
#@-node:ekr.20031218072017.1364:<< Test for @lineending >>
#@+node:ekr.20031218072017.1365:<< Test for print modes directives >>
@ It is valid to have more than one of these directives in the same body text: the more verbose directive takes precedence.
@c

if not print_mode_changed:
    for name in ("verbose","terse","quiet","silent"):
        if dict.has_key(name):
            self.print_mode = name
            print_mode_changed = True
            break
#@-node:ekr.20031218072017.1365:<< Test for print modes directives >>
#@+node:ekr.20031218072017.1366:<< Test for @path >> in tangleScanAllDirectives
if require_path_flag and not old.has_key("path") and dict.has_key("path"):

    k = dict["path"]
    << compute dir and relative_path from s[k:] >>
    if len(dir) > 0:
        base = g.getBaseDirectory() # May return "".
        if dir and len(dir) > 0:
            dir = g.os_path_join(base,dir)
            if g.os_path_isabs(dir):
                << handle absolute @path >>
            elif issue_error_flag and not self.path_warning_given:
                self.path_warning_given = True # supress future warnings
                self.error("ignoring relative path in @path:" + dir)
    elif issue_error_flag and not self.path_warning_given:
        self.path_warning_given = True # supress future warnings
        self.error("ignoring empty @path")
#@+node:ekr.20031218072017.1367:<< compute dir and relative_path from s[k:] >>
j = i = k + len("@path")
i = g.skip_to_end_of_line(s,i)
path = string.strip(s[j:i])

# Remove leading and trailing delims if they exist.
if len(path) > 2 and (
    (path[0]=='<' and path[-1] == '>') or
    (path[0]=='"' and path[-1] == '"') ):
    path = path[1:-1]

dir = relative_path = string.strip(path)
if 0: # 11/14/02: we want a _relative_ path, not an absolute path.
    dir = g.os_path_join(g.app.loadDir,dir)

# g.trace("dir: " + dir)
#@nonl
#@-node:ekr.20031218072017.1367:<< compute dir and relative_path from s[k:] >>
#@+node:ekr.20031218072017.1368:<< handle absolute @path >>
if g.os_path_exists(dir):
    self.tangle_directory = dir
else: # 11/19/02
    self.tangle_directory = g.makeAllNonExistentDirectories(dir)
    if not self.tangle_directory:
        if issue_error_flag and not self.path_warning_given:
            self.path_warning_given = True # supress future warnings
            self.error("@path directory does not exist: " + dir)
            if base and len(base) > 0:
                g.es("relative_path_base_directory: " + base)
            if relative_path and len(relative_path) > 0:
                g.es("relative path in @path directive: " + relative_path)
#@nonl
#@-node:ekr.20031218072017.1368:<< handle absolute @path >>
#@-node:ekr.20031218072017.1366:<< Test for @path >> in tangleScanAllDirectives
#@+node:ekr.20031218072017.1369:<< Test for @pagewidth >>
if not old.has_key("pagewidth") and dict.has_key("pagewidth"):
    
    w = g.scanAtPagewidthDirective(s,dict,issue_error_flag)
    if w and w > 0:
        self.page_width = w
#@nonl
#@-node:ekr.20031218072017.1369:<< Test for @pagewidth >>
#@+node:ekr.20031218072017.1370:<< Test for @root >>
@ 10/27/02: new code:  self.root may not be defined here, so any relative directory specified in the @root node will have no effect unless we have this code.

@c
if self.root_name == None and dict.has_key("root"):

    i = dict["root"]
    # i += len("@root")
    self.setRootFromText(s[i:],issue_error_flag)
#@nonl
#@-node:ekr.20031218072017.1370:<< Test for @root >>
#@+node:ekr.20031218072017.1371:<< Test for @tabwidth >>
if not old.has_key("tabwidth") and dict.has_key("tabwidth"):
    
    w = g.scanAtTabwidthDirective(s,dict,issue_error_flag)
    if w and w != 0:
        self.tab_width = w
#@-node:ekr.20031218072017.1371:<< Test for @tabwidth >>
#@+node:ekr.20031218072017.1372:<< Test for @header and @noheader >>
if old.has_key("header") or old.has_key("noheader"):
    pass # Do nothing more.
    
elif dict.has_key("header") and dict.has_key("noheader"):
    if issue_error_flag:
        g.es("conflicting @header and @noheader directives")

elif dict.has_key("header"):
    self.use_header_flag = True

elif dict.has_key("noheader"):
    self.use_header_flag = False
#@-node:ekr.20031218072017.1372:<< Test for @header and @noheader >>
#@+node:ekr.20031218072017.1373:<< Set self.tangle_directory >>
@ This code sets self.tangle_directory if it has not already been set by an @path directive.

An absolute file name in an @root directive will override the directory set here.
A relative file name gets appended later to the default directory.
That is, the final file name will be g.os_path_join(self.tangle_directory,fileName)
@c

if c.frame and require_path_flag and not self.tangle_directory:
    if self.root_name and len(self.root_name) > 0:
        root_dir = g.os_path_dirname(self.root_name)
    else:
        root_dir = None
    # print "root_dir:", root_dir

    table = ( # This is a precedence table.
        (root_dir,"@root"), 
        (c.tangle_directory,"default tangle"), # Probably should be eliminated.
        (c.frame.openDirectory,"open"))

    base = g.getBaseDirectory() # May return "".

    for dir2, kind in table:
        if dir2 and len(dir2) > 0:
            # print "base,dir:",base,dir
            dir = g.os_path_join(base,dir2)
            if g.os_path_isabs(dir): # Errors may result in relative or invalid path.
                << handle absolute path >>

if not self.tangle_directory and require_path_flag: # issue_error_flag:
    self.pathError("No absolute directory specified by @root, @path or Preferences.")
#@+node:ekr.20031218072017.1374:<< handle absolute path >>
if g.os_path_exists(dir):
    if kind == "@root" and not g.os_path_isabs(root_dir):
        self.tangle_directory = base
    else:
        self.tangle_directory = dir 
    break
else: # 9/25/02
    self.tangle_directory = g.makeAllNonExistentDirectories(dir)
    if not self.tangle_directory:
        # 10/27/02: It is an error for this not to exist now.
        self.error("@root directory does not exist:" + dir)
        if base and len(base) > 0:
            g.es("relative_path_base_directory: " + base)
        if dir2 and len(dir2) > 0:
            g.es(kind + " directory: " + dir2)
#@-node:ekr.20031218072017.1374:<< handle absolute path >>
#@-node:ekr.20031218072017.1373:<< Set self.tangle_directory >>
#@-node:ekr.20031218072017.1360:tangle.scanAllDirectives
#@+node:ekr.20031218072017.1375:scanForTabWidth
# Similar to code in scanAllDirectives.

def scanForTabWidth (self,p):

    c = self.c ; w = c.tab_width

    for p in p.self_and_parents_iter():
        s = p.v.t.bodyString
        dict = g.get_directives_dict(s)
        << set w and break on @tabwidth >>

    c.frame.setTabWidth(w)
#@nonl
#@+node:ekr.20031218072017.1376:<< set w and break on @tabwidth >>
if dict.has_key("tabwidth"):
    
    val = g.scanAtTabwidthDirective(s,dict,issue_error_flag=False)
    if val and val != 0:
        w = val
        break
#@nonl
#@-node:ekr.20031218072017.1376:<< set w and break on @tabwidth >>
#@-node:ekr.20031218072017.1375:scanForTabWidth
#@+node:ekr.20031218072017.1377:scanColorDirectives
def scanColorDirectives(self,p):
    
    """Scan position p and p's ancestors looking for @comment, @language and @root directives,
    setting corresponding colorizer ivars.
    """

    p = p.copy() ; c = self.c
    if c == None: return # self.c may be None for testing.

    language = c.target_language
    self.language = language # 2/2/03
    self.comment_string = None
    self.rootMode = None # None, "code" or "doc"
    
    for p in p.self_and_parents_iter():
        # g.trace(p)
        s = p.v.t.bodyString
        dict = g.get_directives_dict(s)
        << Test for @comment or @language >>
        << Test for @root, @root-doc or @root-code >>

    return self.language # For use by external routines.
#@nonl
#@+node:ekr.20031218072017.1378:<< Test for @comment or @language >>
# 10/17/02: @comment and @language may coexist in the same node.

if dict.has_key("comment"):
    k = dict["comment"]
    self.comment_string = s[k:]

if dict.has_key("language"):
    i = dict["language"]
    language,junk,junk,junk = g.set_language(s,i)
    self.language = language # 2/2/03

if dict.has_key("comment") or dict.has_key("language"):
    break
#@nonl
#@-node:ekr.20031218072017.1378:<< Test for @comment or @language >>
#@+node:ekr.20031218072017.1379:<< Test for @root, @root-doc or @root-code >>
if dict.has_key("root") and not self.rootMode:

    k = dict["root"]
    if g.match_word(s,k,"@root-code"):
        self.rootMode = "code"
    elif g.match_word(s,k,"@root-doc"):
        self.rootMode = "doc"
    else:
        doc = g.app.config.at_root_bodies_start_in_doc_mode
        self.rootMode = g.choose(doc,"doc","code")
#@-node:ekr.20031218072017.1379:<< Test for @root, @root-doc or @root-code >>
#@-node:ekr.20031218072017.1377:scanColorDirectives
#@+node:ekr.20031218072017.1380:Directive utils...
#@+node:EKR.20040504150046.4:g.comment_delims_from_extension
def comment_delims_from_extension(filename):
    
    """
    Return the comment delims corresponding to the filename's extension.

    >>> g.comment_delims_from_extension(".py")
    ('#', None, None)

    >>> g.comment_delims_from_extension(".c")
    ('//', '/*', '*/')
    
    >>> g.comment_delims_from_extension(".html")
    (None, '<!--', '-->')

    """

    root, ext = os.path.splitext(filename)
    if ext == '.tmp':
        root, ext = os.path.splitext(root)
        
    language = g.app.extension_dict.get(ext[1:])
    if ext:
        
        return g.set_delims_from_language(language)
    else:
        g.trace("unknown extension %s" % ext)
        return None,None,None
#@nonl
#@-node:EKR.20040504150046.4:g.comment_delims_from_extension
#@+node:ekr.20031218072017.1381:@language and @comment directives (leoUtils)
#@+node:ekr.20031218072017.1382:set_delims_from_language
# Returns a tuple (single,start,end) of comment delims

def set_delims_from_language(language):

    val = app.language_delims_dict.get(language)
    if val:
        delim1,delim2,delim3 = g.set_delims_from_string(val)
        if delim2 and not delim3:
            return None,delim1,delim2
        else: # 0,1 or 3 params.
            return delim1,delim2,delim3
    else:
        return None, None, None # Indicate that no change should be made
#@-node:ekr.20031218072017.1382:set_delims_from_language
#@+node:ekr.20031218072017.1383:set_delims_from_string
def set_delims_from_string(s):

    """Returns (delim1, delim2, delim2), the delims following the @comment directive.
    
    This code can be called from @languge logic, in which case s can point at @comment"""

    # Skip an optional @comment
    tag = "@comment"
    i = 0
    if g.match_word(s,i,tag):
        i += len(tag)
        
    count = 0 ; delims = [None, None, None]
    while count < 3 and i < len(s):
        i = j = g.skip_ws(s,i)
        while i < len(s) and not g.is_ws(s[i]) and not g.is_nl(s,i):
            i += 1
        if j == i: break
        delims[count] = s[j:i]
        count += 1
        
    # 'rr 09/25/02
    if count == 2: # delims[0] is always the single-line delim.
        delims[2] = delims[1]
        delims[1] = delims[0]
        delims[0] = None

    # 7/8/02: The "REM hack": replace underscores by blanks.
    # 9/25/02: The "perlpod hack": replace double underscores by newlines.
    for i in xrange(0,3):
        if delims[i]:
            delims[i] = string.replace(delims[i],"__",'\n') 
            delims[i] = string.replace(delims[i],'_',' ')

    return delims[0], delims[1], delims[2]
#@nonl
#@-node:ekr.20031218072017.1383:set_delims_from_string
#@+node:ekr.20031218072017.1384:set_language
def set_language(s,i,issue_errors_flag=False):
    
    """Scan the @language directive that appears at s[i:].

    Returns (language, delim1, delim2, delim3)
    """

    tag = "@language"
    # g.trace(g.get_line(s,i))
    assert(i != None)
    assert(g.match_word(s,i,tag))
    i += len(tag) ; i = g.skip_ws(s, i)
    # Get the argument.
    j = i ; i = g.skip_c_id(s,i)
    # Allow tcl/tk.
    arg = string.lower(s[j:i])
    if app.language_delims_dict.get(arg):
        language = arg
        delim1, delim2, delim3 = g.set_delims_from_language(language)
        return language, delim1, delim2, delim3
    
    if issue_errors_flag:
        g.es("ignoring: " + g.get_line(s,i))

    return None, None, None, None,
#@nonl
#@-node:ekr.20031218072017.1384:set_language
#@-node:ekr.20031218072017.1381:@language and @comment directives (leoUtils)
#@+node:ekr.20031218072017.1385:findReference
@ We search the descendents of v looking for the definition node matching name.
There should be exactly one such node (descendents of other definition nodes are not searched).
@c

def findReference(name,root):

    for p in root.subtree_iter():
        assert(p!=root)
        if p.matchHeadline(name) and not p.isAtIgnoreNode():
            return p

    # g.trace("not found:",name,root)
    return root.c.nullPosition()
#@nonl
#@-node:ekr.20031218072017.1385:findReference
#@+node:ekr.20031218072017.1260:get_directives_dict & globalDirectiveList
# The caller passes [root_node] or None as the second arg.  This allows us to distinguish between None and [None].

def get_directives_dict(s,root=None):
    
    """Scans root for @directives found in globalDirectivesList.

    Returns a dict containing pointers to the start of each directive"""

    if root: root_node = root[0]
    dict = {}
    i = 0 ; n = len(s)
    while i < n:
        if s[i] == '@' and i+1 < n:
            << set dict for @ directives >>
        elif root and g.match(s,i,"<<"):
            << set dict["root"] for noweb * chunks >>
        i = g.skip_line(s,i)
    return dict
#@nonl
#@+node:ekr.20031218072017.1261:<< set dict for @ directives >>
j = g.skip_c_id(s,i+1)
word = s[i+1:j]
if word in g.globalDirectiveList:
    if dict.has_key(word):
        # Ignore second value.
        pass
        # g.es("Warning: conflicting values for %s" % (word), color="blue")
    else:
        dict [word] = i
#@nonl
#@-node:ekr.20031218072017.1261:<< set dict for @ directives >>
#@+node:ekr.20031218072017.1262:<< set dict["root"] for noweb * chunks >>
@ The following looks for chunk definitions of the form < < * > > =. If found, we take this to be equivalent to @root filename if the headline has the form @root filename.
@c

i = g.skip_ws(s,i+2)
if i < n and s[i] == '*' :
    i = g.skip_ws(s,i+1) # Skip the '*'
    if g.match(s,i,">>="):
        # < < * > > = implies that @root should appear in the headline.
        i += 3
        if root_node:
            dict["root"]=0 # value not immportant
        else:
            g.es(g.angleBrackets("*") + "= requires @root in the headline")
#@nonl
#@-node:ekr.20031218072017.1262:<< set dict["root"] for noweb * chunks >>
#@-node:ekr.20031218072017.1260:get_directives_dict & globalDirectiveList
#@+node:ekr.20031218072017.1386:getOutputNewline
def getOutputNewline (lineending = None):
    
    """Convert the name of a line ending to the line ending itself.
    Use the output_newline configuration option if no lineending is given.
    """
    
    if lineending:
        s = lineending
    else:
        s = app.config.output_newline

    s = s.lower()
    if s in ( "nl","lf"): s = '\n'
    elif s == "cr": s = '\r'
    elif s == "platform": s = os.linesep  # 12/2/03: emakital
    elif s == "crlf": s = "\r\n"
    else: s = '\n' # Default for erroneous values.
    return s
#@nonl
#@-node:ekr.20031218072017.1386:getOutputNewline
#@+node:ekr.20031218072017.1387:scanAtEncodingDirective
def scanAtEncodingDirective(s,dict):
    
    """Scan the @encoding directive at s[dict["encoding"]:].

    Returns the encoding name or None if the encoding name is invalid.
    """

    k = dict["encoding"]
    i = g.skip_to_end_of_line(s,k)
    j = len("@encoding")
    encoding = s[k+j:i].strip()
    if g.isValidEncoding(encoding):
        # g.trace(encoding)
        return encoding
    else:
        g.es("invalid @encoding:"+encoding,color="red")
        return None
#@nonl
#@-node:ekr.20031218072017.1387:scanAtEncodingDirective
#@+node:ekr.20031218072017.1388:scanAtLineendingDirective
def scanAtLineendingDirective(s,dict):
    
    """Scan the @lineending directive at s[dict["lineending"]:].

    Returns the actual lineending or None if the name of the lineending is invalid.
    """

    k = dict["lineending"]
    i = g.skip_to_end_of_line(s,k)
    j = len("@lineending")
    j = g.skip_ws(s,j)
    e = s[k+j:i].strip()

    if e in ("cr","crlf","lf","nl","platform"):
        lineending = g.getOutputNewline(e)
        # g.trace(e,lineending)
        return lineending
    else:
        # g.es("invalid @lineending directive:"+e,color="red")
        return None
#@nonl
#@-node:ekr.20031218072017.1388:scanAtLineendingDirective
#@+node:ekr.20031218072017.1389:scanAtPagewidthDirective
def scanAtPagewidthDirective(s,dict,issue_error_flag=False):
    
    """Scan the @pagewidth directive at s[dict["pagewidth"]:].

    Returns the value of the width or None if the width is invalid.
    """
    
    k = dict["pagewidth"]
    j = i = k + len("@pagewidth")
    i, val = g.skip_long(s,i)
    if val != None and val > 0:
        # g.trace(val)
        return val
    else:
        if issue_error_flag:
            j = g.skip_to_end_of_line(s,k)
            g.es("ignoring " + s[k:j],color="red")
        return None
#@-node:ekr.20031218072017.1389:scanAtPagewidthDirective
#@+node:ekr.20031218072017.1390:scanAtTabwidthDirective
def scanAtTabwidthDirective(s,dict,issue_error_flag=False):
    
    """Scan the @tabwidth directive at s[dict["tabwidth"]:].

    Returns the value of the width or None if the width is invalid.
    """
    
    k = dict["tabwidth"]
    i = k + len("@tabwidth")
    i, val = g.skip_long(s, i)
    if val != None and val != 0:
        # g.trace(val)
        return val
    else:
        if issue_error_flag:
            i = g.skip_to_end_of_line(s,k)
            g.es("Ignoring " + s[k:i],color="red")
        return None

#@-node:ekr.20031218072017.1390:scanAtTabwidthDirective
#@+node:ekr.20040715155607:scanForAtIgnore
def scanForAtIgnore(c,p):
    
    """Scan position p and its ancestors looking for @ignore directives."""
    
    language = c.target_language

    if c is None or g.top() is None:
        return False # For unit tests.

    for p in p.self_and_parents_iter():
        s = p.bodyString()
        d = g.get_directives_dict(s)
        if d.has_key("ignore"):
            return True

    return False
#@nonl
#@-node:ekr.20040715155607:scanForAtIgnore
#@+node:ekr.20040712084911.1:scanForAtLanguage
def scanForAtLanguage(c,p):
    
    """Scan position p and p's ancestors looking only for @language and @ignore directives.

    Returns the language found, or c.target_language."""
    

    if c and p:
        for p in p.self_and_parents_iter():
            s = p.bodyString()
            d = g.get_directives_dict(s)
            if d.has_key("language"):
                k = d["language"]
                language,delim1,delim2,delim3 = g.set_language(s,k)
                return language # Continue looking for @ignore

    return c.target_language
#@nonl
#@-node:ekr.20040712084911.1:scanForAtLanguage
#@+node:ekr.20031218072017.1391:scanDirectives (utils)
@ Perhaps this routine should be the basis of atFile.scanAllDirectives and tangle.scanAllDirectives, but I am loath to make any further to these two already-infamous routines.  Also, this code does not check for @color and @nocolor directives: leoColor.useSyntaxColoring does that.
@c

def scanDirectives(c,p=None):
    
    """Scan vnode v and v's ancestors looking for directives.

    Returns a dict containing the results, including defaults."""

    if c == None or g.top() == None:
        return {} # For unit tests.
    if p is None:
        p = c.currentPosition()

    << Set local vars >>
    old = {}
    pluginsList = [] # 5/17/03: a list of items for use by plugins.
    for p in p.self_and_parents_iter():
        s = p.v.t.bodyString
        dict = g.get_directives_dict(s)
        << Test for @comment and @language >>
        << Test for @encoding >>
        << Test for @lineending >>
        << Test for @pagewidth >>
        << Test for @path >>
        << Test for @tabwidth >>
        << Test for @wrap and @nowrap >>
        g.doHook("scan-directives",c=c,v=p,s=s,
            old_dict=old,dict=dict,pluginsList=pluginsList)
        old.update(dict)

    if path == None: path = g.getBaseDirectory()

    return {
        "delims"    : (delim1,delim2,delim3),
        "encoding"  : encoding,
        "language"  : language,
        "lineending": lineending,
        "pagewidth" : page_width,
        "path"      : path,
        "tabwidth"  : tab_width,
        "pluginsList": pluginsList,
        "wrap"      : wrap }
#@nonl
#@+node:ekr.20031218072017.1392:<< Set local vars >>
page_width = c.page_width
tab_width  = c.tab_width
language = c.target_language
delim1, delim2, delim3 = g.set_delims_from_language(c.target_language)
path = None
encoding = None # 2/25/03: This must be none so that the caller can set a proper default.
lineending = g.getOutputNewline() # 4/24/03 initialize from config settings.
wrap = app.config.getBoolWindowPref("body_pane_wraps") # 7/7/03: this is a window pref.
#@nonl
#@-node:ekr.20031218072017.1392:<< Set local vars >>
#@+node:ekr.20031218072017.1393:<< Test for @comment and @language >>
# @language and @comment may coexist in @file trees.
# For this to be effective the @comment directive should follow the @language directive.

if not old.has_key("comment") and dict.has_key("comment"):
    k = dict["comment"]
    delim1,delim2,delim3 = g.set_delims_from_string(s[k:])

# Reversion fix: 12/06/02: We must use elif here, not if.
elif not old.has_key("language") and dict.has_key("language"):
    k = dict["language"]
    language,delim1,delim2,delim3 = g.set_language(s,k)
#@nonl
#@-node:ekr.20031218072017.1393:<< Test for @comment and @language >>
#@+node:ekr.20031218072017.1394:<< Test for @encoding >>
if not old.has_key("encoding") and dict.has_key("encoding"):
    
    e = g.scanAtEncodingDirective(s,dict)
    if e:
        encoding = e
#@-node:ekr.20031218072017.1394:<< Test for @encoding >>
#@+node:ekr.20031218072017.1395:<< Test for @lineending >>
if not old.has_key("lineending") and dict.has_key("lineending"):
    
    e = g.scanAtLineendingDirective(s,dict)
    if e:
        lineending = e
#@-node:ekr.20031218072017.1395:<< Test for @lineending >>
#@+node:ekr.20031218072017.1396:<< Test for @pagewidth >>
if dict.has_key("pagewidth") and not old.has_key("pagewidth"):
    
    w = g.scanAtPagewidthDirective(s,dict)
    if w and w > 0:
        page_width = w
#@nonl
#@-node:ekr.20031218072017.1396:<< Test for @pagewidth >>
#@+node:ekr.20031218072017.1397:<< Test for @path >>
if not path and not old.has_key("path") and dict.has_key("path"):

    k = dict["path"]
    << compute relative path from s[k:] >>
    if path and len(path) > 0:
        base = g.getBaseDirectory() # returns "" on error.
        path = g.os_path_join(base,path)
        
#@nonl
#@+node:ekr.20031218072017.1398:<< compute relative path from s[k:] >>
j = i = k + len("@path")
i = g.skip_to_end_of_line(s,i)
path = string.strip(s[j:i])

# Remove leading and trailing delims if they exist.
if len(path) > 2 and (
    (path[0]=='<' and path[-1] == '>') or
    (path[0]=='"' and path[-1] == '"') ):
    path = path[1:-1]

path = string.strip(path)
if 0: # 11/14/02: we want a _relative_ path, not an absolute path.
    path = g.os_path_join(app.loadDir,path)
#@nonl
#@-node:ekr.20031218072017.1398:<< compute relative path from s[k:] >>
#@-node:ekr.20031218072017.1397:<< Test for @path >>
#@+node:ekr.20031218072017.1399:<< Test for @tabwidth >>
if dict.has_key("tabwidth") and not old.has_key("tabwidth"):
    
    w = g.scanAtTabwidthDirective(s,dict)
    if w and w != 0:
        tab_width = w
#@nonl
#@-node:ekr.20031218072017.1399:<< Test for @tabwidth >>
#@+node:ekr.20031218072017.1400:<< Test for @wrap and @nowrap >>
if not old.has_key("wrap") and not old.has_key("nowrap"):
    
    if dict.has_key("wrap"):
        wrap = True
    elif dict.has_key("nowrap"):
        wrap = False
#@nonl
#@-node:ekr.20031218072017.1400:<< Test for @wrap and @nowrap >>
#@-node:ekr.20031218072017.1391:scanDirectives (utils)
#@-node:ekr.20031218072017.1380:Directive utils...
#@+node:ekr.20031218072017.1401:Tests
@ignore
@lineending nl
@encoding iso-8859-1
@pagewidth 80
@tabwidth -8
#@nonl
#@+node:ekr.20031218072017.1402:@rawfile c:\prog\test\rawFileTest.txt
line 1
line 2
last line (no newline)
#@nonl
#@+node:ekr.20031218072017.1403:node 1
node 1 line 1
node 1 line 2
node 1 line 3 (newline)
#@-node:ekr.20031218072017.1403:node 1
#@+node:ekr.20031218072017.1404:node 2
node 2 line 1
node 2 line 2
node 2 line 3 (no newline)
#@nonl
#@-node:ekr.20031218072017.1404:node 2
#@-node:ekr.20031218072017.1402:@rawfile c:\prog\test\rawFileTest.txt
#@+node:ekr.20031218072017.1405:@silentfile c:\prog\test\silentFileTest.txt
line 1
line 2
last line (no newline)
#@nonl
#@-node:ekr.20031218072017.1405:@silentfile c:\prog\test\silentFileTest.txt
#@+node:ekr.20031218072017.1406:@root c:\prog\test\tangleTest.txt
@root c:\prog\test\tangleTest.txt

line 1 Ã
line 2
<< ref >>
line 3
#@nonl
#@+node:ekr.20031218072017.1407:ref
<< ref >>=
ref line 1
ref line 2
#@nonl
#@-node:ekr.20031218072017.1407:ref
#@-node:ekr.20031218072017.1406:@root c:\prog\test\tangleTest.txt
#@-node:ekr.20031218072017.1401:Tests
#@-node:ekr.20031218072017.1341:(scanAllDirectives, scanDirectives, related utils)
#@+node:ekr.20040812170616:Pyrex versions of key classes
#@+node:ekr.20040812154307:@file leoCNodes.pyx
<< tnode/vnode constants >>

@others
#@nonl
#@+node:ekr.20040812155849.1:<< tnode/vnode constants >>
# Define the meaning of status bits in tnodes and vnodes.

# Archived...
cdef enum tnode_vnode_type:
    clonedBit	  = 0x01 # True: vnode has clone mark.
    
    # not used	 = 0x02
    expandedBit = 0x04 # True: vnode is expanded.
    markedBit	  = 0x08 # True: vnode is marked
    orphanBit	  = 0x10 # True: vnode saved in .leo file, not derived file.
    selectedBit = 0x20 # True: vnode is current vnode.
    topBit		    = 0x40 # True: vnode was top vnode when saved.
    
    # Not archived...
    dirtyBit    =	0x060 # Shared.
    richTextBit =	0x080 # Shared. Determines whether we use <bt> or <btr> tags.
    visitedBit	 = 0x100 # Shared.
    
    # Only in tnodes...
    writeBit    = 0x200 # Set: write the tnode.
#@nonl
#@-node:ekr.20040812155849.1:<< tnode/vnode constants >>
#@+node:ekr.20040812154838:class c_tnode
cdef class c_tnode:

    """A Pyrex C class that implements tnodes."""
    
    @others
#@nonl
#@+node:ekr.20040812154838.2:t.__init__
# All params have defaults, so t = tnode() is valid.

def __init__ (self,bodyString=None,headString=None):

    self.cloneIndex = 0 # For Pre-3.12 files.  Zero for @file nodes
    self.fileIndex = None # The immutable file index for this tnode.
    self.insertSpot = None # Location of previous insert point.
    self.scrollBarSpot = None # Previous value of scrollbar position.
    self.selectionLength = 0 # The length of the selected body text.
    self.selectionStart = 0 # The start of the selected body text.
    self.statusBits = 0 # status bits

    # Convert everything to unicode...
    self.headString = g.toUnicode(headString,g.app.tkEncoding)
    self.bodyString = g.toUnicode(bodyString,g.app.tkEncoding)
    
    self.vnodeList = [] # List of all vnodes pointing to this tnode.
    self._firstChild = None
#@nonl
#@-node:ekr.20040812154838.2:t.__init__
#@+node:ekr.20040812154838.3:t.__repr__ & t.__str__
def __repr__ (self):
    
    return "<tnode %d>" % (id(self))
    
def __str__ (self):
    
    return self.__repr__()
#@-node:ekr.20040812154838.3:t.__repr__ & t.__str__
#@+node:ekr.20040812154838.4:For undo
#@+node:ekr.20040812154838.5:t.createUndoInfo
def createUndoInfo (self,copyLinks=True):
    
    """Create a dict containing all info needed to recreate a vnode."""
    
    t = self ; d = {}
    
    # Essential fields.
    d ["t"] = t
    d ["headString"] = t.headString
    d ["bodyString"] = t.bodyString
    d ["vnodeList"]  = t.vnodeList[:]
    d ["statusBits"] = t.statusBits
    d ["firstChild"] = t._firstChild

    try: d ["unknownAttributes"] = t.unknownAttributes
    except: pass
    
    if 0: # These neve change, so no need to save/restore them.
        # In fact, it would be wrong to undo changes made to them!
        d ["cloneIndex"]  = t.cloneIndex
        d ["fileIndex"]  = t.fileIndex

    if 0: # probably not needed for undo.
        d ["insertSpot"]      = t.insertSpot
        d ["scrollBarSpot"]   = t.scrollBarSpot
        d ["selectionLength"] = t.selectionLength
        d ["selectionStart"]  = t.selectionStart

    return d
#@-node:ekr.20040812154838.5:t.createUndoInfo
#@+node:ekr.20040812154838.6:t.restoreUndoInfo
def restoreUndoInfo (self,d):
    
    t = d ["t"] ; assert(t == self)

    t.headString  = d ["headString"]
    t.bodyString  = d ["bodyString"]
    t.vnodeList   = d ["vnodeList"]
    t.statusBits  = d ["statusBits"]
    t._firstChild = d ["firstChild"]

    try:
        t.unknownAttributes = d ["unknownAttributes"]
    except KeyError:
        pass
#@nonl
#@-node:ekr.20040812154838.6:t.restoreUndoInfo
#@-node:ekr.20040812154838.4:For undo
#@+node:ekr.20040812154838.7:Getters
#@+node:ekr.20040812154838.8:getBody
def getBody (self):

    return self.bodyString
#@nonl
#@-node:ekr.20040812154838.8:getBody
#@+node:ekr.20040812154838.9:hasBody
def hasBody (self):

    return self.bodyString and len(self.bodyString) > 0
#@nonl
#@-node:ekr.20040812154838.9:hasBody
#@+node:ekr.20040812154838.10:Status bits
#@+node:ekr.20040812154838.11:isDirty
def isDirty (self):

    return (self.statusBits & self.dirtyBit) != 0
#@nonl
#@-node:ekr.20040812154838.11:isDirty
#@+node:ekr.20040812154838.12:isRichTextBit
def isRichTextBit (self):

    return (self.statusBits & self.richTextBit) != 0
#@nonl
#@-node:ekr.20040812154838.12:isRichTextBit
#@+node:ekr.20040812154838.13:isVisited
def isVisited (self):

    return (self.statusBits & self.visitedBit) != 0
#@nonl
#@-node:ekr.20040812154838.13:isVisited
#@+node:ekr.20040812154838.14:isWriteBit
def isWriteBit (self):

    return (self.statusBits & self.writeBit) != 0
#@nonl
#@-node:ekr.20040812154838.14:isWriteBit
#@-node:ekr.20040812154838.10:Status bits
#@-node:ekr.20040812154838.7:Getters
#@+node:ekr.20040812154838.15:Setters
#@+node:ekr.20040812154838.16:Setting body text
#@+node:ekr.20040812154838.17:setTnodeText
# This sets the text in the tnode from the given string.

def setTnodeText (self,s,encoding="utf-8"):
    
    """Set the body text of a tnode to the given string."""
    
    s = g.toUnicode(s,encoding,reportErrors=True)
    
    if 0: # DANGEROUS:  This automatically converts everything when reading files.

        option = g.app.config.trailing_body_newlines
        
        if option == "one":
            s = s.rstrip() + '\n'
        elif option == "zero":
            s = s.rstrip()
    
    self.bodyString = s
#@nonl
#@-node:ekr.20040812154838.17:setTnodeText
#@+node:ekr.20040812154838.18:setSelection
def setSelection (self,start,length):

    self.selectionStart = start
    self.selectionLength = length
#@nonl
#@-node:ekr.20040812154838.18:setSelection
#@-node:ekr.20040812154838.16:Setting body text
#@+node:ekr.20040812154838.19:Status bits
#@+node:ekr.20040812154838.20:clearDirty
def clearDirty (self):

    self.statusBits = self.statusBits & ~ self.dirtyBit
#@nonl
#@-node:ekr.20040812154838.20:clearDirty
#@+node:ekr.20040812154838.21:clearRichTextBit
def clearRichTextBit (self):

    self.statusBits = self.statusBits & ~ self.richTextBit
#@nonl
#@-node:ekr.20040812154838.21:clearRichTextBit
#@+node:ekr.20040812154838.22:clearVisited
def clearVisited (self):

    self.statusBits = self.statusBits & ~ self.visitedBit
#@nonl
#@-node:ekr.20040812154838.22:clearVisited
#@+node:ekr.20040812154838.23:clearWriteBit
def clearWriteBit (self):

    self.statusBits = self.statusBits & ~ self.writeBit
#@nonl
#@-node:ekr.20040812154838.23:clearWriteBit
#@+node:ekr.20040812154838.24:setDirty
def setDirty (self):

    self.statusBits = self.statusBits | self.dirtyBit
#@nonl
#@-node:ekr.20040812154838.24:setDirty
#@+node:ekr.20040812154838.25:setRichTextBit
def setRichTextBit (self):

    self.statusBits = self.statusBits | self.richTextBit
#@nonl
#@-node:ekr.20040812154838.25:setRichTextBit
#@+node:ekr.20040812154838.26:setVisited
def setVisited (self):

    self.statusBits = self.statusBits | self.visitedBit
#@nonl
#@-node:ekr.20040812154838.26:setVisited
#@+node:ekr.20040812154838.27:setWriteBit
def setWriteBit (self):

    self.statusBits = self.statusBits | self.writeBit
#@nonl
#@-node:ekr.20040812154838.27:setWriteBit
#@-node:ekr.20040812154838.19:Status bits
#@+node:ekr.20040812154838.28:setCloneIndex (used in 3.x)
def setCloneIndex (self, index):

    self.cloneIndex = index
#@nonl
#@-node:ekr.20040812154838.28:setCloneIndex (used in 3.x)
#@+node:ekr.20040812154838.29:setFileIndex
def setFileIndex (self, index):

    self.fileIndex = index
#@nonl
#@-node:ekr.20040812154838.29:setFileIndex
#@-node:ekr.20040812154838.15:Setters
#@-node:ekr.20040812154838:class c_tnode
#@+node:ekr.20040812155849:class c_vnode
cdef class c_vnode:

    """A Pyrex C class that implements vnodes."""

    @others
#@nonl
#@+node:ekr.20040812155849.2:Birth & death
#@+node:ekr.20040812155849.4:v.__init__
def __init__ (self,c,t):

    assert(t)
    << initialize vnode data members >>
#@nonl
#@+node:ekr.20040812155849.5:<< initialize vnode data members >>
self.c = c # The commander for this vnode.
self.t = t # The tnode.
self.statusBits = 0 # status bits

# Structure links.
self._parent = self._next = self._back = None
#@nonl
#@-node:ekr.20040812155849.5:<< initialize vnode data members >>
#@-node:ekr.20040812155849.4:v.__init__
#@+node:ekr.20040812155849.6:v.__repr__ & v.__str__
def __repr__ (self):
    
    if self.t:
        return "<vnode %d:'%s'>" % (id(self),self.cleanHeadString())
    else:
        return "<vnode %d:NULL tnode>" % (id(self))
        
def __str__ (self):
    
    return self.__repr__()
#@nonl
#@-node:ekr.20040812155849.6:v.__repr__ & v.__str__
#@+node:ekr.20040812155849.7:v.dump
def dumpLink (self,link):
    return g.choose(link,link,"<none>")

def dump (self,label=""):
    
    v = self

    if label:
        print '-'*10,label,v
    else:
        print "self    ",v.dumpLink(v)
        print "len(vnodeList)",len(v.t.vnodeList)

    print "_back   ",v.dumpLink(v._back)
    print "_next   ",v.dumpLink(v._next)
    print "_parent ",v.dumpLink(v._parent)
    print "t._child",v.dumpLink(v.t._firstChild)
    
    if 1:
        print "t",v.dumpLink(v.t)
        print "vnodeList"
        for v in v.t.vnodeList:
            print v
#@nonl
#@-node:ekr.20040812155849.7:v.dump
#@-node:ekr.20040812155849.2:Birth & death
#@+node:ekr.20040812155849.8:v.Comparisons
#@+node:ekr.20040812155849.9:findAtFileName (new in 4.2 b3)
def findAtFileName (self,names):
    
    """Return the name following one of the names in nameList.
    Return an empty string."""

    h = self.headString()
    
    if not g.match(h,0,'@'):
        return ""
    
    i = g.skip_id(h,1,'-')
    word = h[:i]
    if word in names and g.match_word(h,0,word):
        name = h[i:].strip()
        # g.trace(word,name)
        return name
    else:
        return ""
#@nonl
#@-node:ekr.20040812155849.9:findAtFileName (new in 4.2 b3)
#@+node:ekr.20040812155849.10:anyAtFileNodeName
def anyAtFileNodeName (self):
    
    """Return the file name following an @file node or an empty string."""

    names = (
        "@file",
        "@thin",   "@file-thin",   "@thinfile",
        "@asis",   "@file-asis",   "@silentfile",
        "@noref",  "@file-noref",  "@rawfile",
        "@nosent", "@file-nosent", "@nosentinelsfile")

    return self.findAtFileName(names)
#@nonl
#@-node:ekr.20040812155849.10:anyAtFileNodeName
#@+node:ekr.20040812155849.11:at...FileNodeName
# These return the filename following @xxx, in v.headString.
# Return the the empty string if v is not an @xxx node.

def atFileNodeName (self):
    names = ("@file"),
    return self.findAtFileName(names)

def atNoSentinelsFileNodeName (self):
    names = ("@nosent", "@file-nosent", "@nosentinelsfile")
    return self.findAtFileName(names)

def atRawFileNodeName (self):
    names = ("@noref", "@file-noref", "@rawfile")
    return self.findAtFileName(names)
    
def atSilentFileNodeName (self):
    names = ("@asis", "@file-asis", "@silentfile")
    return self.findAtFileName(names)
    
def atThinFileNodeName (self):
    names = ("@thin", "@file-thin", "@thinfile")
    return self.findAtFileName(names)
    
# New names, less confusing
def atNoSentFileNodeName (self): return  self.atNoSentinelsFileNodeName()
def atNorefFileNodeName  (self): return  self.atRawFileNodeName()
def atAsisFileNodeName   (self): return  self.tSilentFileNodeName()
#@nonl
#@-node:ekr.20040812155849.11:at...FileNodeName
#@+node:ekr.20040812155849.12:isAtAllNode
def isAtAllNode (self):

    """Returns True if the receiver contains @others in its body at the start of a line."""

    flag, i = g.is_special(self.t.bodyString,0,"@all")
    return flag
#@nonl
#@-node:ekr.20040812155849.12:isAtAllNode
#@+node:ekr.20040812155849.13:isAnyAtFileNode good
def isAnyAtFileNode (self):
    
    """Return True if v is any kind of @file or related node."""
    
    # This routine should be as fast as possible.
    # It is called once for every vnode when writing a file.

    h = self.headString()
    return h and h[0] == '@' and self.anyAtFileNodeName()
#@nonl
#@-node:ekr.20040812155849.13:isAnyAtFileNode good
#@+node:ekr.20040812155849.14:isAt...FileNode
def isAtFileNode (self):
    return g.choose(self.atFileNodeName(),True,False)
    
def isAtNoSentinelsFileNode (self):
    return g.choose(self.atNoSentinelsFileNodeName(),True,False)

def isAtRawFileNode (self): # @file-noref
    return g.choose(self.atRawFileNodeName(),True,False)

def isAtSilentFileNode (self): # @file-asis
    return g.choose(self.atSilentFileNodeName(),True,False)

def isAtThinFileNode (self):
    return g.choose(self.atThinFileNodeName(),True,False)
    
# New names, less confusing:
def isAtNoSentFileNode (self): return self.isAtNoSentinelsFileNode
def isAtNorefFileNode  (self): return self.isAtRawFileNode
def isAtAsisFileNode   (self): return self.isAtSilentFileNode
#@nonl
#@-node:ekr.20040812155849.14:isAt...FileNode
#@+node:ekr.20040812155849.15:isAtIgnoreNode
def isAtIgnoreNode (self):

    """Returns True if the receiver contains @ignore in its body at the start of a line."""

    flag, i = g.is_special(self.t.bodyString, 0, "@ignore")
    return flag
#@nonl
#@-node:ekr.20040812155849.15:isAtIgnoreNode
#@+node:ekr.20040812155849.16:isAtOthersNode
def isAtOthersNode (self):

    """Returns True if the receiver contains @others in its body at the start of a line."""

    flag, i = g.is_special(self.t.bodyString,0,"@others")
    return flag
#@nonl
#@-node:ekr.20040812155849.16:isAtOthersNode
#@+node:ekr.20040812155849.17:matchHeadline
def matchHeadline (self,pattern):

    """Returns True if the headline matches the pattern ignoring whitespace and case.
    
    The headline may contain characters following the successfully matched pattern."""

    h = string.lower(self.headString())
    h = string.replace(h,' ','')
    h = string.replace(h,'\t','')

    s = string.lower(pattern)
    s = string.replace(s,' ','')
    s = string.replace(s,'\t','')

    # ignore characters in the headline following the match
    return s == h[0:len(s)]
#@nonl
#@-node:ekr.20040812155849.17:matchHeadline
#@-node:ekr.20040812155849.8:v.Comparisons
#@+node:ekr.20040812155849.18:Getters (vnode)
#@+node:ekr.20040812155849.19:Tree Traversal getters
#@+node:ekr.20040812155849.20:v.back
# Compatibility routine for scripts

def back (self):

    return self._back
#@nonl
#@-node:ekr.20040812155849.20:v.back
#@+node:ekr.20040812155849.21:v.next
# Compatibility routine for scripts
# Used by p.findAllPotentiallyDirtyNodes.

def next (self):

    return self._next
#@nonl
#@-node:ekr.20040812155849.21:v.next
#@-node:ekr.20040812155849.19:Tree Traversal getters
#@+node:ekr.20040812155849.22:Children
#@+node:ekr.20040812155849.23:v.childIndex
def childIndex(self):
    
    v = self

    if not v._back:
        return 0

    n = 0 ; v = v._back
    while v:
        n = n + 1
        v = v._back
    return n
#@nonl
#@-node:ekr.20040812155849.23:v.childIndex
#@+node:ekr.20040812155849.24:v.firstChild (changed for 4.2)
def firstChild (self):
    
    return self.t._firstChild
#@nonl
#@-node:ekr.20040812155849.24:v.firstChild (changed for 4.2)
#@+node:ekr.20040812155849.25:v.hasChildren & hasFirstChild
def hasChildren (self):
    
    v = self
    return v.firstChild()

def hasFirstChild (self): return self.hasChildren()
#@nonl
#@-node:ekr.20040812155849.25:v.hasChildren & hasFirstChild
#@+node:ekr.20040812155849.26:v.lastChild
def lastChild (self):

    child = self.firstChild()
    while child and child.next():
        child = child.next()
    return child
#@nonl
#@-node:ekr.20040812155849.26:v.lastChild
#@+node:ekr.20040812155849.27:v.nthChild
# childIndex and nthChild are zero-based.

def nthChild (self, n):

    child = self.firstChild()
    if not child: return None
    while n > 0 and child:
        n = n - 1
        child = child.next()
    return child
#@nonl
#@-node:ekr.20040812155849.27:v.nthChild
#@+node:ekr.20040812155849.28:v.numberOfChildren (n)
def numberOfChildren (self):

    n = 0
    child = self.firstChild()
    while child:
        n = n + 1
        child = child.next()
    return n
#@nonl
#@-node:ekr.20040812155849.28:v.numberOfChildren (n)
#@-node:ekr.20040812155849.22:Children
#@+node:ekr.20040812155849.29:Status Bits
#@+node:ekr.20040812155849.30:v.isCloned (4.2)
def isCloned (self):
    
    return len(self.t.vnodeList) > 1
#@nonl
#@-node:ekr.20040812155849.30:v.isCloned (4.2)
#@+node:ekr.20040812155849.31:isDirty
def isDirty (self):

    return self.t.isDirty()
#@nonl
#@-node:ekr.20040812155849.31:isDirty
#@+node:ekr.20040812155849.32:isExpanded
def isExpanded (self):

    return ( self.statusBits & self.expandedBit ) != 0
#@nonl
#@-node:ekr.20040812155849.32:isExpanded
#@+node:ekr.20040812155849.33:isMarked
def isMarked (self):

    return ( self.statusBits & vnode.markedBit ) != 0
#@nonl
#@-node:ekr.20040812155849.33:isMarked
#@+node:ekr.20040812155849.34:isOrphan
def isOrphan (self):

    return ( self.statusBits & vnode.orphanBit ) != 0
#@nonl
#@-node:ekr.20040812155849.34:isOrphan
#@+node:ekr.20040812155849.35:isSelected
def isSelected (self):

    return ( self.statusBits & vnode.selectedBit ) != 0
#@nonl
#@-node:ekr.20040812155849.35:isSelected
#@+node:ekr.20040812155849.36:isTopBitSet
def isTopBitSet (self):

    return ( self.statusBits & self.topBit ) != 0
#@nonl
#@-node:ekr.20040812155849.36:isTopBitSet
#@+node:ekr.20040812155849.37:isVisited
def isVisited (self):

    return ( self.statusBits & vnode.visitedBit ) != 0
#@nonl
#@-node:ekr.20040812155849.37:isVisited
#@+node:ekr.20040812155849.38:status
def status (self):

    return self.statusBits
#@nonl
#@-node:ekr.20040812155849.38:status
#@-node:ekr.20040812155849.29:Status Bits
#@+node:ekr.20040812155849.39:v.bodyString
# Compatibility routine for scripts

def bodyString (self):

    # This message should never be printed and we want to avoid crashing here!
    if not g.isUnicode(self.t.bodyString):
        s = "Leo internal error: not unicode:" + repr(self.t.bodyString)
        print s ; g.es(s,color="red")

    # Make _sure_ we return a unicode string.
    return g.toUnicode(self.t.bodyString,g.app.tkEncoding)
#@-node:ekr.20040812155849.39:v.bodyString
#@+node:ekr.20040812155849.40:v.currentVnode (and c.currentPosition 4.2)
def currentPosition (self):
    return self.c.currentPosition()
        
def currentVnode (self):
    return self.c.currentVnode()
#@nonl
#@-node:ekr.20040812155849.40:v.currentVnode (and c.currentPosition 4.2)
#@+node:ekr.20040812155849.41:v.findRoot (4.2)
def findRoot (self):
    
    return self.c.rootPosition()
#@nonl
#@-node:ekr.20040812155849.41:v.findRoot (4.2)
#@+node:ekr.20040812155849.42:v.headString & v.cleanHeadString
def headString (self):
    
    """Return the headline string."""
    
    # This message should never be printed and we want to avoid crashing here!
    if not g.isUnicode(self.t.headString):
        s = "Leo internal error: not unicode:" + repr(self.t.headString)
        print s ; g.es(s,color="red")
        
    # Make _sure_ we return a unicode string.
    return g.toUnicode(self.t.headString,g.app.tkEncoding)

def cleanHeadString (self):
    
    s = self.headString()
    return g.toEncodedString(s,"ascii") # Replaces non-ascii characters by '?'
#@nonl
#@-node:ekr.20040812155849.42:v.headString & v.cleanHeadString
#@+node:ekr.20040812155849.43:v.directParents (new method in 4.2)
def directParents (self):
    
    """(New in 4.2) Return a list of all direct parent vnodes of a vnode.
    
    This is NOT the same as the list of ancestors of the vnode."""
    
    v = self
    
    if v._parent:
        return v._parent.t.vnodeList
    else:
        return []
#@nonl
#@-node:ekr.20040812155849.43:v.directParents (new method in 4.2)
#@-node:ekr.20040812155849.18:Getters (vnode)
#@+node:ekr.20040812155849.44:v.Link/Unlink/Insert methods (used by file read logic)
# These remain in 4.2: the file read logic calls these before creating positions.
#@nonl
#@+node:ekr.20040812155849.45:v.insertAfter
def insertAfter (self,t=None):

    """Inserts a new vnode after self"""

    if not t:
        t = tnode(headString="NewHeadline")

    v = vnode(self.c,t)
    v.linkAfter(self)

    return v
#@nonl
#@-node:ekr.20040812155849.45:v.insertAfter
#@+node:ekr.20040812155849.46:v.insertAsNthChild
def insertAsNthChild (self,n,t=None):

    """Inserts a new node as the the nth child of the receiver.
    The receiver must have at least n-1 children"""

    if not t:
        t = tnode(headString="NewHeadline")

    v = vnode(self.c,t)
    v.linkAsNthChild(self,n)

    return v
#@nonl
#@-node:ekr.20040812155849.46:v.insertAsNthChild
#@+node:ekr.20040812155849.47:v.linkAfter
def linkAfter (self,v):

    """Link self after v."""
    
    self._parent = v._parent
    self._back = v
    self._next = v._next
    v._next = self
    if self._next:
        self._next._back = self
#@-node:ekr.20040812155849.47:v.linkAfter
#@+node:ekr.20040812155849.48:v.linkAsNthChild
def linkAsNthChild (self,pv,n):

    """Links self as the n'th child of vnode pv"""

    v = self
    # g.trace(v,pv,n)
    v._parent = pv
    if n == 0:
        v._back = None
        v._next = pv.t._firstChild
        if pv.t._firstChild:
            pv.t._firstChild._back = v
        pv.t._firstChild = v
    else:
        prev = pv.nthChild(n-1) # zero based
        assert(prev)
        v._back = prev
        v._next = prev._next
        prev._next = v
        if v._next:
            v._next._back = v
#@nonl
#@-node:ekr.20040812155849.48:v.linkAsNthChild
#@+node:ekr.20040812155849.49:v.linkAsRoot
def linkAsRoot (self,oldRoot):
    
    """Link a vnode as the root node and set the root _position_."""

    v = self ; c = v.c

    # Clear all links except the child link.
    v._parent = None
    v._back = None
    v._next = oldRoot
    
    # Add v to it's tnode's vnodeList. Bug fix: 5/02/04.
    if v not in v.t.vnodeList:
        v.t.vnodeList.append(v)

    # Link in the rest of the tree only when oldRoot != None.
    # Otherwise, we are calling this routine from init code and
    # we want to start with a pristine tree.
    if oldRoot: oldRoot._back = v

    newRoot = position(v,[])
    c.setRootPosition(newRoot)
#@nonl
#@-node:ekr.20040812155849.49:v.linkAsRoot
#@+node:ekr.20040812155849.50:v.moveToRoot
def moveToRoot (self,oldRoot=None):

    """Moves the receiver to the root position"""

    v = self

    v.unlink()
    v.linkAsRoot(oldRoot)
    
    return v
#@nonl
#@-node:ekr.20040812155849.50:v.moveToRoot
#@+node:ekr.20040812155849.51:v.unlink
def unlink (self):

    """Unlinks a vnode from the tree."""

    v = self ; c = v.c

    # g.trace(v._parent," child: ",v.t._firstChild," back: ", v._back, " next: ", v._next)
    
    # Special case the root.
    if v == c.rootPosition().v: # 3/11/04
        assert(v._next)
        newRoot = position(v._next,[])
        c.setRootPosition(newRoot)

    # Clear the links in other nodes.
    if v._back:
        v._back._next = v._next
    if v._next:
        v._next._back = v._back

    if v._parent and v == v._parent.t._firstChild:
        v._parent.t._firstChild = v._next

    # Clear the links in this node.
    v._parent = v._next = v._back = None
    # v.parentsList = []
#@nonl
#@-node:ekr.20040812155849.51:v.unlink
#@-node:ekr.20040812155849.44:v.Link/Unlink/Insert methods (used by file read logic)
#@+node:ekr.20040812155849.52:Setters
#@+node:ekr.20040812155849.53: v.Status bits
#@+node:ekr.20040812155849.54:clearClonedBit
def clearClonedBit (self):

    self.statusBits = self.statusBits & ~ self.clonedBit
#@nonl
#@-node:ekr.20040812155849.54:clearClonedBit
#@+node:ekr.20040812155849.55:clearDirty & clearDirtyJoined (redundant code)
def clearDirty (self):

    v = self
    v.t.clearDirty()

def clearDirtyJoined (self):

    g.trace()
    v = self ; c = v.c
    c.beginUpdate()
    v.t.clearDirty()
    c.endUpdate() # recomputes all icons
#@nonl
#@-node:ekr.20040812155849.55:clearDirty & clearDirtyJoined (redundant code)
#@+node:ekr.20040812155849.56:clearMarked
def clearMarked (self):

    self.statusBits = self.statusBits & ~ self.markedBit
#@-node:ekr.20040812155849.56:clearMarked
#@+node:ekr.20040812155849.57:clearOrphan
def clearOrphan (self):

    self.statusBits = self.statusBits & ~ self.orphanBit
#@nonl
#@-node:ekr.20040812155849.57:clearOrphan
#@+node:ekr.20040812155849.58:clearVisited
def clearVisited (self):

    self.statusBits = self.statusBits & ~ self.visitedBit
#@nonl
#@-node:ekr.20040812155849.58:clearVisited
#@+node:ekr.20040812155849.59:contract & expand & initExpandedBit
def contract(self):

    self.statusBits = self.statusBits & ~ self.expandedBit

def expand(self):

    self.statusBits = self.statusBits | self.expandedBit

def initExpandedBit (self):

    self.statusBits = self.statusBits | self.expandedBit
#@nonl
#@-node:ekr.20040812155849.59:contract & expand & initExpandedBit
#@+node:ekr.20040812155849.60:initStatus
def initStatus (self, status):

    self.statusBits = status
#@nonl
#@-node:ekr.20040812155849.60:initStatus
#@+node:ekr.20040812155849.61:setClonedBit & initClonedBit
def setClonedBit (self):

    self.statusBits = self.statusBits | self.clonedBit

def initClonedBit (self, val):

    if val:
        self.statusBits = self.statusBits | self.clonedBit
    else:
        self.statusBits = self.statusBits & ~ self.clonedBit
#@nonl
#@-node:ekr.20040812155849.61:setClonedBit & initClonedBit
#@+node:ekr.20040812155849.62:v.setMarked & initMarkedBit
def setMarked (self):

    self.statusBits = self.statusBits | self.markedBit

def initMarkedBit (self):

    self.statusBits = self.statusBits | self.markedBit
#@-node:ekr.20040812155849.62:v.setMarked & initMarkedBit
#@+node:ekr.20040812155849.63:setOrphan
def setOrphan (self):

    self.statusBits = self.statusBits | self.orphanBit
#@nonl
#@-node:ekr.20040812155849.63:setOrphan
#@+node:ekr.20040812155849.64:setSelected (vnode)
# This only sets the selected bit.

def setSelected (self):

    self.statusBits = self.statusBits | self.selectedBit
#@nonl
#@-node:ekr.20040812155849.64:setSelected (vnode)
#@+node:ekr.20040812155849.65:t.setVisited
# Compatibility routine for scripts

def setVisited (self):

    self.statusBits = self.statusBits | self.visitedBit
#@nonl
#@-node:ekr.20040812155849.65:t.setVisited
#@-node:ekr.20040812155849.53: v.Status bits
#@+node:ekr.20040812155849.66:v.computeIcon & setIcon
def computeIcon (self):

    val = 0 ; v = self
    if v.t.hasBody(): val = val + 1
    if v.isMarked():  val = val + 2
    if v.isCloned():  val = val + 4
    if v.isDirty():   val = val + 8
    return val
    
def setIcon (self):

    pass # Compatibility routine for old scripts
#@nonl
#@-node:ekr.20040812155849.66:v.computeIcon & setIcon
#@+node:ekr.20040812155849.67:v.initHeadString
def initHeadString (self,s,encoding="utf-8"):
    
    v = self

    s = g.toUnicode(s,encoding,reportErrors=True)
    v.t.headString = s
#@nonl
#@-node:ekr.20040812155849.67:v.initHeadString
#@+node:ekr.20040812155849.68:v.setSelection
def setSelection (self, start, length):

    self.t.setSelection ( start, length )
#@nonl
#@-node:ekr.20040812155849.68:v.setSelection
#@+node:ekr.20040812155849.69:v.setTnodeText
def setTnodeText (self,s,encoding="utf-8"):
    
    return self.t.setTnodeText(s,encoding)
#@nonl
#@-node:ekr.20040812155849.69:v.setTnodeText
#@+node:ekr.20040812155849.70:v.trimTrailingLines
def trimTrailingLines (self):

    """Trims trailing blank lines from a node.
    
    It is surprising difficult to do this during Untangle."""

    v = self
    body = v.bodyString()
    # g.trace(body)
    lines = string.split(body,'\n')
    i = len(lines) - 1 ; changed = False
    while i >= 0:
        line = lines[i]
        j = g.skip_ws(line,0)
        if j + 1 == len(line):
            del lines[i]
            i = i - 1 ; changed = True
        else: break
    if changed:
        body = string.join(body,'') + '\n' # Add back one last newline.
        # g.trace(body)
        v.setBodyStringOrPane(body)
        # Don't set the dirty bit: it would just be annoying.
#@-node:ekr.20040812155849.70:v.trimTrailingLines
#@-node:ekr.20040812155849.52:Setters
#@+node:ekr.20040812155849.71:For undo
#@+node:ekr.20040812155849.72:v.createUndoInfo
def createUndoInfo (self):
    
    """Create a dict containing all info needed to recreate a vnode for undo."""
    
    v = self ; d = {}
    
    # Copy all ivars.
    d ["v"] = v
    d ["statusBits"] = v.statusBits
    d ["parent"] = v._parent
    d ["next"] = v._next
    d ["back"] = v._back
    # The tnode never changes so there is no need to save it here.
    
    try: d ["unknownAttributes"] = v.unknownAttributes
    except: pass

    return d
#@nonl
#@-node:ekr.20040812155849.72:v.createUndoInfo
#@+node:ekr.20040812155849.73:v.restoreUndoInfo
def restoreUndoInfo (self,d):
    
    """Restore all ivars saved in dict d."""
    
    v = d ["v"] ; assert(v == self)

    v.statusBits = d ["statusBits"]
    v._parent    = d ["parent"] 
    v._next      = d ["next"] 
    v._back      = d ["back"]
    
    try:
        v.unknownAttributes = d ["unknownAttributes"]
    except KeyError:
        pass
#@nonl
#@-node:ekr.20040812155849.73:v.restoreUndoInfo
#@-node:ekr.20040812155849.71:For undo
#@-node:ekr.20040812155849:class c_vnode
#@+node:ekr.20040812154341:class c_position
cdef class c_position:
    
    """A Pyrex C class representing a position."""
    
    @others
#@+node:ekr.20040812154341.2: ctor & other special methods...
#@+node:ekr.20040812154341.3:p.__cmp__
def __cmp__(self,p2):

    """Return 0 if two postions are equivalent."""

    # Use p.equal if speed is crucial.
    p1 = self

    if p2 is None: # Allow tests like "p == None"
        if p1.v: return 1 # not equal
        else:    return 0 # equal

    # Check entire stack quickly.
    # The stack contains vnodes, so this is not a recursive call.
    if p1.v != p2.v or p1.stack != p2.stack:
        return 1 # notEqual

    # This is slow: do this last!
    if p1.childIndex() != p2.childIndex():
        # Disambiguate clones having the same parents.
        return 1 # notEqual

    return 0 # equal
#@nonl
#@-node:ekr.20040812154341.3:p.__cmp__
#@+node:ekr.20040812154341.4:p.__getattr__  ON:  must be ON if use_plugins
def __getattr__ (self,attr):
    
    """Convert references to p.t into references to p.v.t.
    
    N.B. This automatically keeps p.t in synch with p.v.t."""

    if attr=="t":
        return self.v.t
    elif attr=="__del__":
        # This works around a Python 2.2 wierdness.
        return AttributeError # Silently ignore this.
    else:
        # Only called when normal lookup fails.
        print "unknown position attribute:",attr
        # import traceback ; traceback.print_stack()
        raise AttributeError
#@nonl
#@-node:ekr.20040812154341.4:p.__getattr__  ON:  must be ON if use_plugins
#@+node:ekr.20040812154341.5:p.__init__
def __init__ (self,v,stack,trace=True):

    """Create a new position."""
    
    if v: self.c = v.c
    else: self.c = g.top()
    self.v = v
    assert(v is None or v.t)
    self.stack = stack[:] # Creating a copy here is safest and best.

    g.app.positions = g.app.positions + 1
    
    if g.app.tracePositions and trace:
        g.trace("%-25s %-25s %s" % (
            g.callerName(4),g.callerName(3),g.callerName(2)),align=10)
    
    # Note: __getattr__ implements p.t.
#@nonl
#@-node:ekr.20040812154341.5:p.__init__
#@+node:ekr.20040812154341.6:p.__nonzero__
@
The test "if p" is the _only_ correct way to test whether a position p is valid.
In particular, tests like "if p is None" or "if p is not None" will not work properly.
@c

def __nonzero__ ( self):
    
    """Return True if a position is valid."""
    
    # if g.app.trace: "__nonzero__",self.v

    return self.v is not None
#@nonl
#@-node:ekr.20040812154341.6:p.__nonzero__
#@+node:ekr.20040812154341.7:p.__str__ and p.__repr__
def __str__ (self):
    
    p = self
    
    if p.v:
        return "<pos %d lvl: %d [%d] %s>" % (id(p),p.level(),len(p.stack),p.cleanHeadString())
    else:
        return "<pos %d        [%d] None>" % (id(p),len(p.stack))
        
def __repr__ (self):
    
    return self.__str__()
#@nonl
#@-node:ekr.20040812154341.7:p.__str__ and p.__repr__
#@+node:ekr.20040812154341.8:p.copy
# Using this routine can generate huge numbers of temporary positions during a tree traversal.

def copy (self):
    
    """"Return an independent copy of a position."""
    
    if g.app.tracePositions:
        g.trace("%-25s %-25s %s" % (
            g.callerName(4),g.callerName(3),g.callerName(2)),align=10)

    return position(self.v,self.stack,trace=False)
#@nonl
#@-node:ekr.20040812154341.8:p.copy
#@+node:ekr.20040812154341.9:p.dump & p.vnodeListIds
def dumpLink (self,link):

    return g.choose(link,link,"<none>")

def dump (self,label=""):
    
    p = self
    print '-'*10,label,p
    if p.v:
        p.v.dump() # Don't print a label
        
def vnodeListIds (self):
    
    p = self
    # return [id(v) for v in p.v.t.vnodeList]
    val = []
    for v in p.v.t.vnodeList:
        val.append(id(v))
#@nonl
#@-node:ekr.20040812154341.9:p.dump & p.vnodeListIds
#@+node:ekr.20040812154341.10:p.equal & isEqual
def equal(self,p2):

    """Return True if two postions are equivalent.
    
    Use this method when the speed comparisons is crucial
    
    N.B. Unlike __cmp__, p2 must not be None.
    
    >>> c = g.top() ; p = c.currentPosition() ; root = c.rootPosition()
    >>> n = g.app.positions
    >>> assert p.equal(p.copy()) is True
    >>> assert p.equal(root) is False
    >>> assert g.app.positions == n + 1
    >>> 
    """

    p1 = self
    
    # if g.app.trace: "equal",p1.v,p2.v

    # Check entire stack quickly.
    # The stack contains vnodes, so this does not call p.__cmp__.
    return (
        p1.v == p2.v and
        p1.stack == p2.stack and
        p1.childIndex() == p2.childIndex())
        
def isEqual(self,p2):
    return self.equal(p2)
#@nonl
#@-node:ekr.20040812154341.10:p.equal & isEqual
#@-node:ekr.20040812154341.2: ctor & other special methods...
#@+node:ekr.20040812154341.11:Getters
#@+node:ekr.20040812154341.12: vnode proxies
#@+node:ekr.20040812154341.13:p.Comparisons
def anyAtFileNodeName         (self): return self.v.anyAtFileNodeName()
def atFileNodeName            (self): return self.v.atFileNodeName()
def atNoSentinelsFileNodeName (self): return self.v.atNoSentinelsFileNodeName()
def atRawFileNodeName         (self): return self.v.atRawFileNodeName()
def atSilentFileNodeName      (self): return self.v.atSilentFileNodeName()
def atThinFileNodeName        (self): return self.v.atThinFileNodeName()

# New names, less confusing
def atNoSentFileNodeName (self): return self.atNoSentinelsFileNodeName()
def atNorefFileNodeName  (self): return self.atRawFileNodeName()
def atAsisFileNodeName   (self): return self.atSilentFileNodeName()

def isAnyAtFileNode         (self): return self.v.isAnyAtFileNode()
def isAtAllNode             (self): return self.v.isAtAllNode()
def isAtFileNode            (self): return self.v.isAtFileNode()
def isAtIgnoreNode          (self): return self.v.isAtIgnoreNode()
def isAtNoSentinelsFileNode (self): return self.v.isAtNoSentinelsFileNode()
def isAtOthersNode          (self): return self.v.isAtOthersNode()
def isAtRawFileNode         (self): return self.v.isAtRawFileNode()
def isAtSilentFileNode      (self): return self.v.isAtSilentFileNode()
def isAtThinFileNode        (self): return self.v.isAtThinFileNode()

# New names, less confusing:
def isAtNoSentFileNode (self): return self.isAtNoSentinelsFileNode()
def isAtNorefFileNode  (self): return self.isAtRawFileNode()
def isAtAsisFileNode   (self): return self.isAtSilentFileNode()

# Utilities.
def matchHeadline (self,pattern): return self.v.matchHeadline(pattern)
#@nonl
#@-node:ekr.20040812154341.13:p.Comparisons
#@+node:ekr.20040812154341.14:p.Extra Attributes
def extraAttributes (self):
    
    return self.v.extraAttributes()

def setExtraAttributes (self,data):

    return self.v.setExtraAttributes(data)
#@nonl
#@-node:ekr.20040812154341.14:p.Extra Attributes
#@+node:ekr.20040812154341.15:p.Headline & body strings
def bodyString (self):
    
    return self.v.bodyString()

def headString (self):
    
    return self.v.headString()
    
def cleanHeadString (self):
    
    return self.v.cleanHeadString()
#@-node:ekr.20040812154341.15:p.Headline & body strings
#@+node:ekr.20040812154341.16:p.Status bits
def isDirty     (self): return self.v.isDirty()
def isExpanded  (self): return self.v.isExpanded()
def isMarked    (self): return self.v.isMarked()
def isOrphan    (self): return self.v.isOrphan()
def isSelected  (self): return self.v.isSelected()
def isTopBitSet (self): return self.v.isTopBitSet()
def isVisited   (self): return self.v.isVisited()
def status      (self): return self.v.status()
#@nonl
#@-node:ekr.20040812154341.16:p.Status bits
#@+node:ekr.20040812154341.17:p.edit_text
def edit_text (self):
    
    # New in 4.3 beta 3: let the tree classes do all the work.
    
    p = self ; c = p.c
    
    return c.frame.tree.edit_text(p)
#@nonl
#@-node:ekr.20040812154341.17:p.edit_text
#@+node:ekr.20040812154341.18:p.directParents
def directParents (self):
    
    return self.v.directParents()
#@-node:ekr.20040812154341.18:p.directParents
#@+node:ekr.20040812154341.19:p.childIndex
def childIndex(self):
    
    p = self ; v = p.v
    
    # This is time-critical code!
    
    # 3/25/04: Much faster code:
    if not v or not v._back:
        return 0

    n = 0 ; v = v._back
    while v:
        n = n + 1
        v = v._back

    return n
#@nonl
#@-node:ekr.20040812154341.19:p.childIndex
#@-node:ekr.20040812154341.12: vnode proxies
#@+node:ekr.20040812154341.20:children
#@+node:ekr.20040812154341.21:p.hasChildren
def hasChildren(self):
    
    p = self
    # g.trace(p,p.v)
    return p.v and p.v.t and p.v.t._firstChild
#@nonl
#@-node:ekr.20040812154341.21:p.hasChildren
#@+node:ekr.20040812154341.22:p.numberOfChildren
def numberOfChildren (self):
    
    return self.v.numberOfChildren()
#@-node:ekr.20040812154341.22:p.numberOfChildren
#@-node:ekr.20040812154341.20:children
#@+node:ekr.20040812154341.23:p.exists
def exists(self,c):
    
    """Return True if a position exists in c's tree"""
    
    p = self.copy()
    
    # This code must be fast.
    root = c.rootPosition()
    while p:
        if p == root:
            return True
        if p.hasParent():
            p.moveToParent()
        else:
            p.moveToBack()
        
    return False
#@nonl
#@-node:ekr.20040812154341.23:p.exists
#@+node:ekr.20040812154341.24:p.findRoot
def findRoot (self):
    
    return self.c.frame.rootPosition()
#@nonl
#@-node:ekr.20040812154341.24:p.findRoot
#@+node:ekr.20040812154341.25:p.getX & vnode compatibility traversal routines
# These methods are useful abbreviations.
# Warning: they make copies of positions, so they should be used _sparingly_

def getBack          (self): return self.copy().moveToBack()
def getFirstChild    (self): return self.copy().moveToFirstChild()
def getLastChild     (self): return self.copy().moveToLastChild()
def getLastNode      (self): return self.copy().moveToLastNode()
def getLastVisible   (self): return self.copy().moveToLastVisible()
def getNext          (self): return self.copy().moveToNext()
def getNodeAfterTree (self): return self.copy().moveToNodeAfterTree()
def getNthChild    (self,n): return self.copy().moveToNthChild(n)
def getParent        (self): return self.copy().moveToParent()
def getThreadBack    (self): return self.copy().moveToThreadBack()
def getThreadNext    (self): return self.copy().moveToThreadNext()
def getVisBack       (self): return self.copy().moveToVisBack()
def getVisNext       (self): return self.copy().moveToVisNext()

# These are efficient enough now that iterators are the normal way to traverse the tree!

def back          (self): return self.getBack()
def firstChild    (self): return self.getFirstChild()
def lastChild     (self): return self.getLastChild()
def lastNode      (self): return self.getLastNode()
def next          (self): return self.getNext()
def nodeAfterTree (self): return self.getNodeAfterTree()
def nthChild      (self): return self.getNthChild()
def parent        (self): return self.getParent()
def threadBack    (self): return self.getThreadBack()
def threadNext    (self): return self.getThreadNext()
def visBack       (self): return self.getVisBack()
def visNext       (self): return self.getVisNext()
#@nonl
#@-node:ekr.20040812154341.25:p.getX & vnode compatibility traversal routines
#@+node:ekr.20040812154341.26:p.hasX
def hasBack(self):
    return self.v and self.v._back

def hasFirstChild (self): return self.hasChildren()
    
def hasNext(self):
    return self.v and self.v._next
    
def hasParent(self):
    return self.v and self.v._parent is not None
    
def hasThreadBack(self):
    return self.hasParent() or self.hasBack() # Much cheaper than computing the actual value.
    
def hasVisBack(self): return self.hasThreadBack()
#@nonl
#@+node:ekr.20040812154341.27:hasThreadNext (the only complex hasX method)
def hasThreadNext(self):

    p = self ; v = p.v
    if not p.v: return False

    if v.t._firstChild or v._next:
        return True
    else:
        n = len(p.stack)-1
        v,n = p.vParentWithStack(v,p.stack,n)
        while v:
            if v._next:
                return True
            v,n = p.vParentWithStack(v,p.stack,n)
        return False

def hasVisNext (self): return self.hasThreadNext()
#@nonl
#@-node:ekr.20040812154341.27:hasThreadNext (the only complex hasX method)
#@-node:ekr.20040812154341.26:p.hasX
#@+node:ekr.20040812154341.28:p.isAncestorOf
def isAncestorOf (self, p2):
    
    p = self
    
    if 0: # Avoid the copies made in the iterator.
        for p3 in p2.parents_iter():
            if p3 == p:
                return True

    # Avoid calling p.copy() or copying the stack.
    v2 = p2.v ; n = len(p2.stack)-1
        # Major bug fix 7/22/04: changed len(p.stack) to len(p2.stack.)
    v2,n = p2.vParentWithStack(v2,p2.stack,n)
    while v2:
        if v2 == p.v:
            return True
        v2,n = p2.vParentWithStack(v2,p2.stack,n)

    return False
#@nonl
#@-node:ekr.20040812154341.28:p.isAncestorOf
#@+node:ekr.20040812154341.29:p.isCurrentPosition & isRootPosition
#@+node:ekr.20040812154341.30:isCurrentPosition
def isCurrentPosition (self):
    
    p = self ; c = p.c
    
    return c.isCurrentPosition(p)
    
#@-node:ekr.20040812154341.30:isCurrentPosition
#@+node:ekr.20040812154341.31:isRootPosition
def isRootPosition (self):
    
    p = self ; c = p.c
    
    return c.isRootPosition(p)
#@nonl
#@-node:ekr.20040812154341.31:isRootPosition
#@-node:ekr.20040812154341.29:p.isCurrentPosition & isRootPosition
#@+node:ekr.20040812154341.32:p.isCloned
def isCloned (self):
    
    return len(self.v.t.vnodeList) > 1
#@nonl
#@-node:ekr.20040812154341.32:p.isCloned
#@+node:ekr.20040812154341.33:p.isRoot
def isRoot (self):
    
    p = self

    return not p.hasParent() and not p.hasBack()
#@nonl
#@-node:ekr.20040812154341.33:p.isRoot
#@+node:ekr.20040812154341.34:p.isVisible
def isVisible (self):
    
    """Return True if all of a position's parents are expanded."""

    # v.isVisible no longer exists.
    p = self

    # Avoid calling p.copy() or copying the stack.
    v = p.v ; n = len(p.stack)-1

    v,n = p.vParentWithStack(v,p.stack,n)
    while v:
        if not v.isExpanded():
            return False
        v,n = p.vParentWithStack(v,p.stack,n)

    return True
#@nonl
#@-node:ekr.20040812154341.34:p.isVisible
#@+node:ekr.20040812154341.35:p.lastVisible & oldLastVisible
def oldLastVisible(self):
    """Move to the last visible node of the entire tree."""
    p = self.c.rootPosition()
    assert(p.isVisible())
    last = p.copy()
    while 1:
        if g.app.debug: g.trace(last)
        p.moveToVisNext()
        if not p: break
        last = p.copy()
    return last
        
def lastVisible(self):
    """Move to the last visible node of the entire tree."""
    p = self.c.rootPosition()
    # Move to the last top-level node.
    while p.hasNext():
        if g.app.debug: g.trace(p)
        p.moveToNext()
    assert(p.isVisible())
    # Move to the last visible child.
    while p.hasChildren() and p.isExpanded():
        if g.app.debug: g.trace(p)
        p.moveToLastChild()
    assert(p.isVisible())
    if g.app.debug: g.trace(p)
    return p
#@nonl
#@-node:ekr.20040812154341.35:p.lastVisible & oldLastVisible
#@+node:ekr.20040812154341.36:p.level & simpleLevel
def simpleLevel(self):
    
    p = self ; level = 0
    for parent in p.parents_iter():
        level = level + 1
    return level

def level(self,verbose=False):
    
    # if g.app.debug: simpleLevel = self.simpleLevel()
    
    p = self ; level = 0
    if not p: return level
        
    # Avoid calling p.copy() or copying the stack.
    v = p.v ; n = len(p.stack)-1
    while 1:
        assert(p)
        v,n = p.vParentWithStack(v,p.stack,n)
        if v:
            level = level + 1
            if verbose: g.trace(level,"level %2d, n: %2d" % (level,n))
        else:
            if verbose: g.trace(level,"level %2d, n: %2d" % (level,n))
            # if g.app.debug: assert(level==simpleLevel)
            break
    return level
#@nonl
#@-node:ekr.20040812154341.36:p.level & simpleLevel
#@-node:ekr.20040812154341.11:Getters
#@+node:ekr.20040812154341.37:Setters
#@+node:ekr.20040812154341.38:vnode proxies
#@+node:ekr.20040812154341.39: Status bits
# Clone bits are no longer used.
# Dirty bits are handled carefully by the position class.

def clearMarked  (self):
    g.doHook("clear-mark",c=self.c,p=self)
    return self.v.clearMarked()

def clearOrphan  (self): return self.v.clearOrphan()
def clearVisited (self): return self.v.clearVisited()

def contract (self): return self.v.contract()
def expand   (self): return self.v.expand()

def initExpandedBit    (self): return self.v.initExpandedBit()
def initMarkedBit      (self): return self.v.initMarkedBit()
def initStatus (self, status): return self.v.initStatus()
    
def setMarked (self):
    g.doHook("set-mark",c=self.c,p=self)
    return self.v.setMarked()

def setOrphan   (self): return self.v.setOrphan()
def setSelected (self): return self.v.setSelected()
def setVisited  (self): return self.v.setVisited()
#@nonl
#@-node:ekr.20040812154341.39: Status bits
#@+node:ekr.20040812154341.40:p.computeIcon & p.setIcon
def computeIcon (self):
    
    return self.v.computeIcon()
    
def setIcon (self):

    pass # Compatibility routine for old scripts
#@nonl
#@-node:ekr.20040812154341.40:p.computeIcon & p.setIcon
#@+node:ekr.20040812154341.41:p.setSelection
def setSelection (self,start,length):

    return self.v.setSelection(start,length)
#@nonl
#@-node:ekr.20040812154341.41:p.setSelection
#@+node:ekr.20040812154341.42:p.trimTrailingLines
def trimTrailingLines (self):

    return self.v.trimTrailingLines()
#@nonl
#@-node:ekr.20040812154341.42:p.trimTrailingLines
#@+node:ekr.20040812154341.43:p.setTnodeText
def setTnodeText (self,s,encoding="utf-8"):
    
    return self.v.setTnodeText(s,encoding)
#@nonl
#@-node:ekr.20040812154341.43:p.setTnodeText
#@-node:ekr.20040812154341.38:vnode proxies
#@+node:ekr.20040812154341.44:Head & body text (position)
#@+node:ekr.20040812154341.45:p.appendStringToBody
def appendStringToBody (self,s,encoding="utf-8"):
    
    p = self
    if not s: return
    
    body = p.bodyString()
    assert(g.isUnicode(body))
    s = g.toUnicode(s,encoding)

    p.setBodyStringOrPane(body + s,encoding)
#@nonl
#@-node:ekr.20040812154341.45:p.appendStringToBody
#@+node:ekr.20040812154341.46:p.setBodyStringOrPane & p.setBodyTextOrPane
def setBodyStringOrPane (self,s,encoding="utf-8"):

    p = self ; v = p.v ; c = p.c
    if not c or not v: return

    s = g.toUnicode(s,encoding)
    if p == c.currentPosition():
        # 7/23/04: Revert to previous code, but force an empty selection.
        c.frame.body.setSelectionAreas(s,None,None)
        c.frame.body.setTextSelection(None)
        # This code destoys all tags, so we must recolor.
        c.recolor()
        
    # Keep the body text in the tnode up-to-date.
    if v.t.bodyString != s:
        v.setTnodeText(s)
        v.t.setSelection(0,0)
        p.setDirty()
        if not c.isChanged():
            c.setChanged(True)

def setBodyTextOrPane (self): return self.setBodyStringOrPane() # Compatibility with old scripts
#@nonl
#@-node:ekr.20040812154341.46:p.setBodyStringOrPane & p.setBodyTextOrPane
#@+node:ekr.20040812154341.47:p.setHeadString & p.initHeadString
def setHeadString (self,s,encoding="utf-8"):
    
    p = self
    p.v.initHeadString(s,encoding)
    p.setDirty()
    
def initHeadString (self,s,encoding="utf-8"):
    
    p = self
    p.v.initHeadString(s,encoding)
#@-node:ekr.20040812154341.47:p.setHeadString & p.initHeadString
#@+node:ekr.20040812154341.48:p.setHeadStringOrHeadline
def setHeadStringOrHeadline (self,s,encoding="utf-8"):

    p = self ; c = p.c
    
    t = p.edit_text()
    
    p.initHeadString(s,encoding)

    if t:
        
        state = t.cget("state")
        # g.trace(state,s)
        t.configure(state="normal")
        t.delete("1.0","end")
        t.insert("end",s)
        t.configure(state=state)

    p.setDirty()
#@nonl
#@-node:ekr.20040812154341.48:p.setHeadStringOrHeadline
#@+node:ekr.20040812154341.49:p.scriptSetBodyString
def scriptSetBodyString (self,s,encoding="utf-8"):
    
    """Update the body string for the receiver.
    
    Should be called only from scripts: does NOT update body text."""

    self.v.t.bodyString = g.toUnicode(s,encoding)
#@nonl
#@-node:ekr.20040812154341.49:p.scriptSetBodyString
#@-node:ekr.20040812154341.44:Head & body text (position)
#@+node:ekr.20040812154341.50:Visited bits
#@+node:ekr.20040812154341.51:p.clearAllVisited
# Compatibility routine for scripts.

def clearAllVisited (self):
    
    for p in self.allNodes_iter():
        p.clearVisited()
#@nonl
#@-node:ekr.20040812154341.51:p.clearAllVisited
#@+node:ekr.20040812154341.52:p.clearVisitedInTree
# Compatibility routine for scripts.

def clearVisitedInTree (self):
    
    for p in self.self_and_subtree_iter():
        p.clearVisited()
#@-node:ekr.20040812154341.52:p.clearVisitedInTree
#@+node:ekr.20040812154341.53:p.clearAllVisitedInTree (4.2)
def clearAllVisitedInTree (self):
    
    for p in self.self_and_subtree_iter():
        p.v.clearVisited()
        p.v.t.clearVisited()
        p.v.t.clearWriteBit()
#@nonl
#@-node:ekr.20040812154341.53:p.clearAllVisitedInTree (4.2)
#@-node:ekr.20040812154341.50:Visited bits
#@+node:ekr.20040812154341.54:p.Dirty bits
#@+node:ekr.20040812154341.55:p.clearDirty
def clearDirty (self):

    p = self
    p.v.clearDirty()
#@nonl
#@-node:ekr.20040812154341.55:p.clearDirty
#@+node:ekr.20040812154341.56:p.findAllPotentiallyDirtyNodes
def findAllPotentiallyDirtyNodes(self):
    
    p = self 
    
    # Start with all nodes in the vnodeList.
    nodes = []
    newNodes = p.v.t.vnodeList[:]

    # Add nodes until no more are added.
    while newNodes:
        addedNodes = []
        nodes.extend(newNodes)
        for v in newNodes:
            for v2 in v.t.vnodeList:
                if v2 not in nodes and v2 not in addedNodes:
                    addedNodes.append(v2)
                for v3 in v2.directParents():
                    if v3 not in nodes and v3 not in addedNodes:
                        addedNodes.append(v3)
        newNodes = addedNodes[:]

    # g.trace(len(nodes))
    return nodes
#@nonl
#@-node:ekr.20040812154341.56:p.findAllPotentiallyDirtyNodes
#@+node:ekr.20040812154341.57:p.setAllAncestorAtFileNodesDirty
def setAllAncestorAtFileNodesDirty (self,setDescendentsDirty=False):

    p = self ; c = p.c
    changed = False
    
    # Calculate all nodes that are joined to v or parents of such nodes.
    nodes = p.findAllPotentiallyDirtyNodes()
    
    if setDescendentsDirty:
        # N.B. Only mark _direct_ descendents of nodes.
        # Using the findAllPotentiallyDirtyNodes algorithm would mark way too many nodes.
        for p2 in p.subtree_iter():
            # Only @thin nodes need to be marked.
            if p2.v not in nodes and p2.isAtThinFileNode():
                nodes.append(p2.v)
    
    c.beginUpdate()
    if 1: # update...
        count = 0 # for debugging.
        for v in nodes:
            if not v.t.isDirty() and v.isAnyAtFileNode():
                # g.trace(v)
                changed = True
                v.t.setDirty() # Do not call v.setDirty here!
                count = count + 1
        # g.trace(count,changed)
    c.endUpdate(changed)
    return changed
#@nonl
#@-node:ekr.20040812154341.57:p.setAllAncestorAtFileNodesDirty
#@+node:ekr.20040812154341.58:p.setDirty
# Ensures that all ancestor and descentent @file nodes are marked dirty.
# It is much safer to do it this way.

def setDirty (self,setDescendentsDirty=True):

    p = self ; c = p.c
    
    # g.trace(g.app.count) ; g.app.count += 1

    c.beginUpdate()
    if 1: # update...
        changed = False
        if not p.v.t.isDirty():
            p.v.t.setDirty()
            changed = True
        # N.B. This must be called even if p.v is already dirty.
        # Typing can change the @ignore state!
        if p.setAllAncestorAtFileNodesDirty(setDescendentsDirty):
            changed = True
    c.endUpdate(changed)

    return changed
#@nonl
#@-node:ekr.20040812154341.58:p.setDirty
#@+node:ekr.20040812154341.59:p.inAtIgnoreRange
def inAtIgnoreRange (self):
    
    """Returns True if position p or one of p's parents is an @ignore node."""
    
    p = self
    
    for p in p.self_and_parents_iter():
        if p.isAtIgnoreNode():
            return True

    return False
#@nonl
#@-node:ekr.20040812154341.59:p.inAtIgnoreRange
#@-node:ekr.20040812154341.54:p.Dirty bits
#@-node:ekr.20040812154341.37:Setters
#@+node:ekr.20040812154341.60:File Conversion
@
- convertTreeToString and moreHead can't be vnode methods because they uses level().
- moreBody could be anywhere: it may as well be a postion method.
#@+node:ekr.20040812154341.61:convertTreeToString
def convertTreeToString (self):
    
    """Convert a positions  suboutline to a string in MORE format."""

    p = self ; level1 = p.level()
    
    array = []
    for p in p.self_and_subtree_iter():
        array.append(p.moreHead(level1)+'\n')
        body = p.moreBody()
        if body:
            array.append(body +'\n')

    return ''.join(array)
#@-node:ekr.20040812154341.61:convertTreeToString
#@+node:ekr.20040812154341.62:moreHead
def moreHead (self, firstLevel,useVerticalBar=False):
    
    """Return the headline string in MORE format."""

    p = self

    level = self.level() - firstLevel
    plusMinus = g.choose(p.hasChildren(), "+", "-")
    
    return "%s%s %s" % ('\t'*level,plusMinus,p.headString())
#@nonl
#@-node:ekr.20040812154341.62:moreHead
#@+node:ekr.20040812154341.63:moreBody
@ 
    + test line
    - test line
    \ test line
    test line +
    test line -
    test line \
    More lines...
@c

def moreBody (self):

    """Returns the body string in MORE format.  
    
    Inserts a backslash before any leading plus, minus or backslash."""

    p = self ; array = []
    lines = string.split(p.bodyString(),'\n')
    for s in lines:
        i = g.skip_ws(s,0)
        if i < len(s) and s[i] in ('+','-','\\'):
            s = s[:i] + '\\' + s[i:]
        array.append(s)
    return '\n'.join(array)
#@nonl
#@-node:ekr.20040812154341.63:moreBody
#@-node:ekr.20040812154341.60:File Conversion
#@+node:ekr.20040812154341.82:p.Moving, Inserting, Deleting, Cloning, Sorting (position)
#@+node:ekr.20040812154341.83:p.doDelete
@ This is the main delete routine.  It deletes the receiver's entire tree from the screen.  Because of the undo command we never actually delete vnodes or tnodes.
@c

def doDelete (self,newPosition):

    """Deletes position p from the outline.  May be undone.

    Returns newPosition."""

    p = self ; c = p.c

    assert(newPosition != p)
    p.setDirty() # Mark @file nodes dirty!
    p.unlink()
    p.deleteLinksInTree()
    c.selectVnode(newPosition)
    
    return newPosition

#@-node:ekr.20040812154341.83:p.doDelete
#@+node:ekr.20040812154341.84:p.insertAfter
def insertAfter (self,t=None):

    """Inserts a new position after self.
    
    Returns the newly created position."""
    
    p = self ; c = p.c
    p2 = self.copy()

    if not t:
        t = tnode(headString="NewHeadline")

    p2.v = vnode(c,t)
    p2.v.iconVal = 0
    p2.linkAfter(p)

    return p2
#@nonl
#@-node:ekr.20040812154341.84:p.insertAfter
#@+node:ekr.20040812154341.85:p.insertAsLastChild
def insertAsLastChild (self,t=None):

    """Inserts a new vnode as the last child of self.
    
    Returns the newly created position."""
    
    p = self
    n = p.numberOfChildren()

    if not t:
        t = tnode(headString="NewHeadline")
    
    return p.insertAsNthChild(n,t)
#@nonl
#@-node:ekr.20040812154341.85:p.insertAsLastChild
#@+node:ekr.20040812154341.86:p.insertAsNthChild
def insertAsNthChild (self,n,t=None):

    """Inserts a new node as the the nth child of self.
    self must have at least n-1 children.
    
    Returns the newly created position."""
    
    p = self ; c = p.c
    p2 = self.copy()

    if not t:
        t = tnode(headString="NewHeadline")
    
    p2.v = vnode(c,t)
    p2.v.iconVal = 0
    p2.linkAsNthChild(p,n)

    return p2
#@nonl
#@-node:ekr.20040812154341.86:p.insertAsNthChild
#@+node:ekr.20040812154341.87:p.moveToRoot
def moveToRoot (self,oldRoot=None):

    """Moves a position to the root position."""

    p = self # Do NOT copy the position!
    p.unlink()
    p.linkAsRoot(oldRoot)
    
    return p
#@nonl
#@-node:ekr.20040812154341.87:p.moveToRoot
#@+node:ekr.20040812154341.88:p.clone
def clone (self,back):
    
    """Create a clone of back.
    
    Returns the newly created position."""
    
    p = self ; c = p.c
    
    # g.trace(p,back)

    p2 = back.copy()
    p2.v = vnode(c,back.v.t)
    p2.linkAfter(back)

    return p2
#@nonl
#@-node:ekr.20040812154341.88:p.clone
#@+node:ekr.20040812154341.89:p.copyTreeAfter, copyTreeTo
# This is used by unit tests.

def copyTreeAfter(self):
    p = self
    p2 = p.insertAfter()
    p.copyTreeFromSelfTo(p2)
    return p2
    
def copyTreeFromSelfTo(self,p2):
    p = self
    p2.v.t.headString = p.headString()
    p2.v.t.bodyString = p.bodyString()
    for child in p.children_iter(copy=True):
        child2 = p2.insertAsLastChild()
        child.copyTreeFromSelfTo(child2)
#@nonl
#@-node:ekr.20040812154341.89:p.copyTreeAfter, copyTreeTo
#@+node:ekr.20040812154341.90:p.moveAfter
def moveAfter (self,a):

    """Move a position after position a."""
    
    p = self ; c = p.c # Do NOT copy the position!
    p.unlink()
    p.linkAfter(a)
    
    # Moving a node after another node can create a new root node.
    if not a.hasParent() and not a.hasBack():
        c.setRootPosition(a)

    return p
#@nonl
#@-node:ekr.20040812154341.90:p.moveAfter
#@+node:ekr.20040812154341.91:p.moveToLastChildOf
def moveToLastChildOf (self,parent):

    """Move a position to the last child of parent."""

    p = self # Do NOT copy the position!

    p.unlink()
    n = p.numberOfChildren()
    p.linkAsNthChild(parent,n)

    # Moving a node can create a new root node.
    if not parent.hasParent() and not parent.hasBack():
        p.c.setRootPosition(parent)
        
    return p
#@-node:ekr.20040812154341.91:p.moveToLastChildOf
#@+node:ekr.20040812154341.92:p.moveToNthChildOf
def moveToNthChildOf (self,parent,n):

    """Move a position to the nth child of parent."""

    p = self ; c = p.c # Do NOT copy the position!
    
    # g.trace(p,parent,n)

    p.unlink()
    p.linkAsNthChild(parent,n)
    
    # Moving a node can create a new root node.
    if not parent.hasParent() and not parent.hasBack():
        c.setRootPosition(parent)

    return p
#@-node:ekr.20040812154341.92:p.moveToNthChildOf
#@+node:ekr.20040812154341.93:p.sortChildren
def sortChildren (self):
    
    p = self

    # Create a list of (headline,position) tuples
    pairs = []
    for child in p.children_iter():
        pairs.append((string.lower(child.headString()),child.copy())) # do we need to copy?

    # Sort the list on the headlines.
    pairs.sort()

    # Move the children.
    index = 0
    for headline,child in pairs:
        child.moveToNthChildOf(p,index)
        index = index + 1
#@nonl
#@-node:ekr.20040812154341.93:p.sortChildren
#@+node:ekr.20040812154341.94:p.validateOutlineWithParent
# This routine checks the structure of the receiver's tree.

def validateOutlineWithParent (self,pv):
    
    p = self
    result = True # optimists get only unpleasant surprises.
    parent = p.getParent()
    childIndex = p.childIndex()
    
    # g.trace(p,parent,pv)
    << validate parent ivar >>
    << validate childIndex ivar >>
    << validate x ivar >>

    # Recursively validate all the children.
    for child in p.children_iter():
        r = child.validateOutlineWithParent(p)
        if not r: result = False

    return result
#@nonl
#@+node:ekr.20040812154341.95:<< validate parent ivar >>
if parent != pv:
    p.invalidOutline( "Invalid parent link: " + repr(parent))
#@nonl
#@-node:ekr.20040812154341.95:<< validate parent ivar >>
#@+node:ekr.20040812154341.96:<< validate childIndex ivar >>
if pv:
    if childIndex < 0:
        p.invalidOutline ( "missing childIndex" + childIndex )
    elif childIndex >= pv.numberOfChildren():
        p.invalidOutline ( "missing children entry for index: " + childIndex )
elif childIndex < 0:
    p.invalidOutline ( "negative childIndex" + childIndex )
#@nonl
#@-node:ekr.20040812154341.96:<< validate childIndex ivar >>
#@+node:ekr.20040812154341.97:<< validate x ivar >>
if not p.v.t and pv:
    self.invalidOutline ( "Empty t" )
#@nonl
#@-node:ekr.20040812154341.97:<< validate x ivar >>
#@-node:ekr.20040812154341.94:p.validateOutlineWithParent
#@+node:ekr.20040812154341.98:p.invalidOutline
def invalidOutline (self, message):
    
    p = self

    if p.hasParent():
        node = p.parent()
    else:
        node = p

    g.alert("invalid outline: %s\n%s" % (message,node))
#@nonl
#@-node:ekr.20040812154341.98:p.invalidOutline
#@-node:ekr.20040812154341.82:p.Moving, Inserting, Deleting, Cloning, Sorting (position)
#@+node:ekr.20040812154341.99:p.moveToX
@
These routines change self to a new position "in place".
That is, these methods must _never_ call p.copy().

When moving to a nonexistent position, these routines simply set p.v = None,
leaving the p.stack unchanged. This allows the caller to "undo" the effect of
the invalid move by simply restoring the previous value of p.v.

These routines all return self on exit so the following kind of code will work:
    after = p.copy().moveToNodeAfterTree()
#@nonl
#@+node:ekr.20040812154341.100:p.moveToBack
def moveToBack (self):
    
    """Move self to its previous sibling."""
    
    p = self

    p.v = p.v and p.v._back
    
    return p
#@nonl
#@-node:ekr.20040812154341.100:p.moveToBack
#@+node:ekr.20040812154341.101:p.moveToFirstChild (pushes stack for cloned nodes)
def moveToFirstChild (self):

    """Move a position to it's first child's position."""
    
    p = self

    if p:
        child = p.v.t._firstChild
        if child:
            if p.isCloned():
                p.stack.append(p.v)
                # g.trace("push",p.v,p)
            p.v = child
        else:
            p.v = None
        
    return p

#@-node:ekr.20040812154341.101:p.moveToFirstChild (pushes stack for cloned nodes)
#@+node:ekr.20040812154341.102:p.moveToLastChild (pushes stack for cloned nodes)
def moveToLastChild (self):
    
    """Move a position to it's last child's position."""
    
    p = self

    if p:
        if p.v.t._firstChild:
            child = p.v.lastChild()
            if p.isCloned():
                p.stack.append(p.v)
                # g.trace("push",p.v,p)
            p.v = child
        else:
            p.v = None
            
    return p
#@-node:ekr.20040812154341.102:p.moveToLastChild (pushes stack for cloned nodes)
#@+node:ekr.20040812154341.103:p.moveToLastNode (Big improvement for 4.2)
def moveToLastNode (self):
    
    """Move a position to last node of its tree.
    
    N.B. Returns p if p has no children."""
    
    p = self
    
    # Huge improvement for 4.2.
    while p.hasChildren():
        p.moveToLastChild()

    return p
#@nonl
#@-node:ekr.20040812154341.103:p.moveToLastNode (Big improvement for 4.2)
#@+node:ekr.20040812154341.104:p.moveToNext
def moveToNext (self):
    
    """Move a position to its next sibling."""
    
    p = self
    
    p.v = p.v and p.v._next
    
    return p
#@nonl
#@-node:ekr.20040812154341.104:p.moveToNext
#@+node:ekr.20040812154341.105:p.moveToNodeAfterTree
def moveToNodeAfterTree (self):
    
    """Move a position to the node after the position's tree."""
    
    p = self
    
    while p:
        if p.hasNext():
            p.moveToNext()
            break
        p.moveToParent()

    return p
#@-node:ekr.20040812154341.105:p.moveToNodeAfterTree
#@+node:ekr.20040812154341.106:p.moveToNthChild (pushes stack for cloned nodes)
def moveToNthChild (self,n):
    
    p = self
    
    if p:
        child = p.v.nthChild(n) # Must call vnode method here!
        if child:
            if p.isCloned():
                p.stack.append(p.v)
                # g.trace("push",p.v,p)
            p.v = child
        else:
            p.v = None
            
    return p
#@nonl
#@-node:ekr.20040812154341.106:p.moveToNthChild (pushes stack for cloned nodes)
#@+node:ekr.20040812154341.107:p.moveToParent (pops stack when multiple parents)
def moveToParent (self):
    
    """Move a position to its parent position."""
    
    p = self
    
    # if p.v._parent: g.trace(len(p.v._parent.t.vnodeList),p.v._parent)

    if p.v._parent and len(p.v._parent.t.vnodeList) == 1:
        p.v = p.v._parent
    elif p.stack:
        p.v = p.stack.pop()
        # g.trace("pop",p.v,p)
    else:
        p.v = None

    return p
#@nonl
#@-node:ekr.20040812154341.107:p.moveToParent (pops stack when multiple parents)
#@+node:ekr.20040812154341.108:p.moveToThreadBack
def moveToThreadBack (self):
    
    """Move a position to it's threadBack position."""

    p = self

    if p.hasBack():
        p.moveToBack()
        p.moveToLastNode()
    else:
        p.moveToParent()

    return p
#@nonl
#@-node:ekr.20040812154341.108:p.moveToThreadBack
#@+node:ekr.20040812154341.109:p.moveToThreadNext
def moveToThreadNext (self):
    
    """Move a position to the next a position in threading order."""
    
    p = self

    if p:
        if p.v.t._firstChild:
            p.moveToFirstChild()
        elif p.v._next:
            p.moveToNext()
        else:
            p.moveToParent()
            while p:
                if p.v._next:
                    p.moveToNext()
                    break #found
                p.moveToParent()
            # not found.
                
    return p
#@nonl
#@-node:ekr.20040812154341.109:p.moveToThreadNext
#@+node:ekr.20040812154341.110:p.moveToVisBack
def moveToVisBack (self):
    
    """Move a position to the position of the previous visible node."""

    p = self
    
    if p:
        p.moveToThreadBack()
        while p and not p.isVisible():
            p.moveToThreadBack()

    assert(not p or p.isVisible())
    return p
#@nonl
#@-node:ekr.20040812154341.110:p.moveToVisBack
#@+node:ekr.20040812154341.111:p.moveToVisNext
def moveToVisNext (self):
    
    """Move a position to the position of the next visible node."""

    p = self

    p.moveToThreadNext()
    while p and not p.isVisible():
        p.moveToThreadNext()
            
    return p
#@nonl
#@-node:ekr.20040812154341.111:p.moveToVisNext
#@-node:ekr.20040812154341.99:p.moveToX
#@+node:ekr.20040812154341.112:p.utils...
#@+node:ekr.20040812154341.113:p.vParentWithStack
# A crucial utility method.
# The p.level(), p.isVisible() and p.hasThreadNext() methods show how to use this method.

<< about the vParentWithStack utility method >>

def vParentWithStack(self,v,stack,n):
    
    """A utility that allows the computation of p.v without calling p.copy().
    
    v,stack[:n] correspond to p.v,p.stack for some intermediate position p.

    Returns (v,n) such that v,stack[:n] correpond to the parent position of p."""

    if not v:
        return None,n
    elif v._parent and len(v._parent.t.vnodeList) == 1:
        return v._parent,n # don't change stack.
    elif stack and n >= 0:
        return self.stack[n],n-1 # simulate popping the stack.
    else:
        return None,n
#@nonl
#@+node:ekr.20040812154341.114:<< about the vParentWithStack utility method >>
@ 
This method allows us to simulate calls to p.parent() without generating any intermediate data.

For example, the code below will compute the same values for list1 and list2:

# The first way depends on the call to p.copy:
list1 = []
p=p.copy() # odious.
while p:
    p = p.moveToParent()
    if p: list1.append(p.v)

# The second way uses p.vParentWithStack to avoid all odious intermediate data.

list2 = []
n = len(p.stack)-1
v,n = p.vParentWithStack(v,p.stack,n)
while v:
    list2.append(v)
    v,n = p.vParentWithStack(v,p.stack,n)

#@-node:ekr.20040812154341.114:<< about the vParentWithStack utility method >>
#@-node:ekr.20040812154341.113:p.vParentWithStack
#@+node:ekr.20040812154341.115:p.restoreLinksInTree
def restoreLinksInTree (self):

    """Restore links when undoing a delete node operation."""
    
    root = p = self

    if p.v not in p.v.t.vnodeList:
        p.v.t.vnodeList.append(p.v)
        
    for p in root.children_iter():
        p.restoreLinksInTree()
#@nonl
#@-node:ekr.20040812154341.115:p.restoreLinksInTree
#@+node:ekr.20040812154341.116:p.deleteLinksInTree & allies
def deleteLinksInTree (self):
    
    """Delete and otherwise adjust links when deleting node."""
    
    root = self

    root.deleteLinksInSubtree()
    
    for p in root.children_iter():
        p.adjustParentLinksInSubtree(parent=root)
#@nonl
#@+node:ekr.20040812154341.117:p.deleteLinksInSubtree
def deleteLinksInSubtree (self):

    root = p = self

    # Delete p.v from the vnodeList
    if p.v in p.v.t.vnodeList:
        p.v.t.vnodeList.remove(p.v)
        assert(p.v not in p.v.t.vnodeList)
        # g.trace("deleted",p.v,p.vnodeListIds())
    else:
        # g.trace("not in vnodeList",p.v,p.vnodeListIds())
        pass

    if len(p.v.t.vnodeList) == 0:
        # This node is not shared by other nodes.
        for p in root.children_iter():
            p.deleteLinksInSubtree()
#@nonl
#@-node:ekr.20040812154341.117:p.deleteLinksInSubtree
#@+node:ekr.20040812154341.118:p.adjustParentLinksInSubtree
def adjustParentLinksInSubtree (self,parent):
    
    root = p = self
    
    assert(parent)
    
    if p.v._parent and parent.v.t.vnodeList and p.v._parent not in parent.v.t.vnodeList:
        p.v._parent = parent.v.t.vnodeList[0]
        
    for p in root.children_iter():
        p.adjustParentLinksInSubtree(parent=root)
#@nonl
#@-node:ekr.20040812154341.118:p.adjustParentLinksInSubtree
#@-node:ekr.20040812154341.116:p.deleteLinksInTree & allies
#@-node:ekr.20040812154341.112:p.utils...
#@+node:ekr.20040812154341.119:p.Link/Unlink methods
# These remain in 4.2:  linking and unlinking does not depend on position.

# These are private routines:  the position class does not define proxies for these.
#@nonl
#@+node:ekr.20040812154341.120:p.linkAfter
def linkAfter (self,after):

    """Link self after v."""
    
    p = self
    # g.trace(p,after)
    
    p.stack = after.stack[:] # 3/12/04
    p.v._parent = after.v._parent
    
    # Add v to it's tnode's vnodeList.
    if p.v not in p.v.t.vnodeList:
        p.v.t.vnodeList.append(p.v)
    
    p.v._back = after.v
    p.v._next = after.v._next
    
    after.v._next = p.v
    
    if p.v._next:
        p.v._next._back = p.v

    if 0:
        g.trace('-'*20,after)
        p.dump(label="p")
        after.dump(label="back")
        if p.hasNext(): p.next().dump(label="next")
#@nonl
#@-node:ekr.20040812154341.120:p.linkAfter
#@+node:ekr.20040812154341.121:p.linkAsNthChild
def linkAsNthChild (self,parent,n):

    """Links self as the n'th child of vnode pv"""
    
    # g.trace(self,parent,n)
    p = self

    # Recreate the stack using the parent.
    p.stack = parent.stack[:] 
    if parent.isCloned():
        p.stack.append(parent.v)

    p.v._parent = parent.v

    # Add v to it's tnode's vnodeList.
    if p.v not in p.v.t.vnodeList:
        p.v.t.vnodeList.append(p.v)

    if n == 0:
        child1 = parent.v.t._firstChild
        p.v._back = None
        p.v._next = child1
        if child1:
            child1._back = p.v
        parent.v.t._firstChild = p.v
    else:
        prev = parent.nthChild(n-1) # zero based
        assert(prev)
        p.v._back = prev.v
        p.v._next = prev.v._next
        prev.v._next = p.v
        if p.v._next:
            p.v._next._back = p.v
            
    if 0:
        g.trace('-'*20)
        p.dump(label="p")
        parent.dump(label="parent")
#@nonl
#@-node:ekr.20040812154341.121:p.linkAsNthChild
#@+node:ekr.20040812154341.122:p.linkAsRoot
def linkAsRoot (self,oldRoot):
    
    """Link self as the root node."""
    
    # g.trace(self,oldRoot)

    p = self ; v = p.v
    if oldRoot: oldRootVnode = oldRoot.v
    else:       oldRootVnode = None
    
    p.stack = [] # Clear the stack.
    
    # Clear all links except the child link.
    v._parent = None
    v._back = None
    v._next = oldRootVnode # Bug fix: 3/12/04
    
    # Add v to it's tnode's vnodeList. Bug fix: 5/02/04.
    if v not in v.t.vnodeList:
        v.t.vnodeList.append(v)

    # Link in the rest of the tree only when oldRoot != None.
    # Otherwise, we are calling this routine from init code and
    # we want to start with a pristine tree.
    if oldRoot:
        oldRoot.v._back = v # Bug fix: 3/12/04

    p.c.setRootPosition(p)
    
    if 0:
        p.dump(label="root")
#@-node:ekr.20040812154341.122:p.linkAsRoot
#@+node:ekr.20040812154341.123:p.unlink
def unlink (self):

    """Unlinks a position p from the tree before moving or deleting.
    
    The p.v._fistChild link does NOT change."""

    p = self ; v = p.v ; parent = p.parent()
    
    # Note:  p.parent() is not necessarily the same as v._parent.
    
    if parent:
        assert(p.v and p.v._parent in p.v.directParents())
        assert(parent.v in p.v.directParents())

    # g.trace("parent",parent," child:",v.t._firstChild," back:",v._back, " next:",v._next)
    
    # Special case the root.
    if p == p.c.rootPosition():
        assert(p.v._next)
        p.c.setRootPosition(p.next())
    
    # Remove v from it's tnode's vnodeList.
    vnodeList = v.t.vnodeList
    if v in vnodeList:
        vnodeList.remove(v)
    assert(v not in vnodeList)
    
    # Reset the firstChild link in its direct father.
    if parent and parent.v.t._firstChild == v:
        parent.v.t._firstChild = v._next

    # Do _not_ delete the links in any child nodes.

    # Clear the links in other nodes.
    if v._back: v._back._next = v._next
    if v._next: v._next._back = v._back

    # Unlink _this_ node.
    v._parent = v._next = v._back = None

    if 0:
        g.trace('-'*20)
        p.dump(label="p")
        if parent: parent.dump(label="parent")
#@-node:ekr.20040812154341.123:p.unlink
#@-node:ekr.20040812154341.119:p.Link/Unlink methods
#@-node:ekr.20040812154341:class c_position
#@-node:ekr.20040812154307:@file leoCNodes.pyx
#@+node:ekr.20040812155849.74:v.Iterators (Can't use yield in pyrex)
#@+node:ekr.20040812155849.75:self_subtree_iter
def subtree_iter(self):

    """Return all nodes of self's tree in outline order."""
    
    v = self

    if v:
        yield v
        child = v.t._firstChild
        while child:
            for v1 in child.subtree_iter():
                yield v1
            child = child.next()
            
self_and_subtree_iter = subtree_iter
#@nonl
#@-node:ekr.20040812155849.75:self_subtree_iter
#@+node:ekr.20040812155849.76:unique_subtree_iter
def unique_subtree_iter(self,marks=None):

    """Return all vnodes in self's tree, discarding duplicates """
    
    v = self

    if marks == None: marks = {}

    if v and v not in marks:
        marks[v] = v
        yield v
        if v.t._firstChild:
            for v1 in v.t._firstChild.unique_subtree_iter(marks):
                yield v1
        v = v._next
        while v:
            for v in v.unique_subtree_iter(marks):
                yield v
            v = v._next
            
self_and_unique_subtree_iter = unique_subtree_iter
#@nonl
#@-node:ekr.20040812155849.76:unique_subtree_iter
#@-node:ekr.20040812155849.74:v.Iterators (Can't use yield in pyrex)
#@+node:ekr.20040812154341.64:p.Iterators (Can't use yield in pyrex)
@ 3/18/04: a crucial optimization:

Iterators make no copies at all if they would return an empty sequence.
@c

@others
#@nonl
#@+node:ekr.20040812154341.65:p.tnodes_iter & unique_tnodes_iter
def tnodes_iter(self):
    
    """Return all tnode's in a positions subtree."""
    
    p = self
    for p in p.self_and_subtree_iter():
        yield p.v
        
def unique_tnodes_iter(self):
    
    """Return all unique tnode's in a positions subtree."""
    
    p = self
    marks = {}
    for p in p.self_and_subtree_iter():
        if p.v not in marks:
            marks[p.v] = p.v
            yield p.v
#@nonl
#@-node:ekr.20040812154341.65:p.tnodes_iter & unique_tnodes_iter
#@+node:ekr.20040812154341.66:p.vnodes_iter & unique_vnodes_iter
def vnodes_iter(self):
    
    """Return all vnode's in a positions subtree."""
    
    p = self
    for p in p.self_and_subtree_iter():
        yield p.v
        
def unique_vnodes_iter(self):
    
    """Return all unique vnode's in a positions subtree."""
    
    p = self
    marks = {}
    for p in p.self_and_subtree_iter():
        if p.v not in marks:
            marks[p.v] = p.v
            yield p.v
#@nonl
#@-node:ekr.20040812154341.66:p.vnodes_iter & unique_vnodes_iter
#@+node:ekr.20040812154341.67:p.allNodes_iter
class allNodes_iter_class:

    """Returns a list of positions in the entire outline."""

    @others

def allNodes_iter (self,copy=False):
    
    return self.allNodes_iter_class(self,copy)
#@nonl
#@+node:ekr.20040812154341.68:__init__ & __iter__
def __init__(self,p,copy):

    self.first = p.c.rootPosition().copy()
    self.p = None
    self.copy = copy
    
def __iter__(self):

    return self
#@-node:ekr.20040812154341.68:__init__ & __iter__
#@+node:ekr.20040812154341.69:next
def next(self):
    
    if self.first:
        self.p = self.first
        self.first = None

    elif self.p:
        self.p.moveToThreadNext()

    if self.p:
        if self.copy: return self.p.copy()
        else:         return self.p
    else: raise StopIteration
#@nonl
#@-node:ekr.20040812154341.69:next
#@-node:ekr.20040812154341.67:p.allNodes_iter
#@+node:ekr.20040812154341.70:p.subtree_iter
class subtree_iter_class:

    """Returns a list of positions in a subtree, possibly including the root of the subtree."""

    @others

def subtree_iter (self,copy=False):
    
    return self.subtree_iter_class(self,copy,includeSelf=False)
    
def self_and_subtree_iter (self,copy=False):
    
    return self.subtree_iter_class(self,copy,includeSelf=True)
#@nonl
#@+node:ekr.20040812154341.71:__init__ & __iter__
def __init__(self,p,copy,includeSelf):
    
    if includeSelf:
        self.first = p.copy()
        self.after = p.nodeAfterTree()
    elif p.hasChildren():
        self.first = p.copy().moveToFirstChild() 
        self.after = p.nodeAfterTree()
    else:
        self.first = None
        self.after = None

    self.p = None
    self.copy = copy
    
def __iter__(self):

    return self
#@-node:ekr.20040812154341.71:__init__ & __iter__
#@+node:ekr.20040812154341.72:next
def next(self):
    
    if self.first:
        self.p = self.first
        self.first = None

    elif self.p:
        self.p.moveToThreadNext()

    if self.p and self.p != self.after:
        if self.copy: return self.p.copy()
        else:         return self.p
    else:
        raise StopIteration
#@nonl
#@-node:ekr.20040812154341.72:next
#@-node:ekr.20040812154341.70:p.subtree_iter
#@+node:ekr.20040812154341.73:p.children_iter
class children_iter_class:

    """Returns a list of children of a position."""

    @others

def children_iter (self,copy=False):
    
    return self.children_iter_class(self,copy)
#@nonl
#@+node:ekr.20040812154341.74:__init__ & __iter__
def __init__(self,p,copy):

    if p.hasChildren():
        self.first = p.copy().moveToFirstChild()
    else:
        self.first = None

    self.p = None
    self.copy = copy

def __iter__(self):
    
    return self
#@-node:ekr.20040812154341.74:__init__ & __iter__
#@+node:ekr.20040812154341.75:next
def next(self):
    
    if self.first:
        self.p = self.first
        self.first = None

    elif self.p:
        self.p.moveToNext()

    if self.p:
        if self.copy: return self.p.copy()
        else:         return self.p
    else: raise StopIteration
#@nonl
#@-node:ekr.20040812154341.75:next
#@-node:ekr.20040812154341.73:p.children_iter
#@+node:ekr.20040812154341.76:p.parents_iter
class parents_iter_class:

    """Returns a list of positions of a position."""

    @others

def parents_iter (self,copy=False):
    
    p = self

    return self.parents_iter_class(self,copy,includeSelf=False)
    
def self_and_parents_iter(self,copy=False):
    
    return self.parents_iter_class(self,copy,includeSelf=True)
#@nonl
#@+node:ekr.20040812154341.77:__init__ & __iter__
def __init__(self,p,copy,includeSelf):

    if includeSelf:
        self.first = p.copy()
    elif p.hasParent():
        self.first = p.copy().moveToParent()
    else:
        self.first = None

    self.p = None
    self.copy = copy

def __iter__(self):

    return self
#@nonl
#@-node:ekr.20040812154341.77:__init__ & __iter__
#@+node:ekr.20040812154341.78:next
def next(self):
    
    if self.first:
        self.p = self.first
        self.first = None

    elif self.p:
        self.p.moveToParent()

    if self.p:
        if self.copy: return self.p.copy()
        else:         return self.p
    else:
        raise StopIteration
#@-node:ekr.20040812154341.78:next
#@-node:ekr.20040812154341.76:p.parents_iter
#@+node:ekr.20040812154341.79:p.siblings_iter
class siblings_iter_class:

    """Returns a list of siblings of a position."""

    @others

def siblings_iter (self,copy=False,following=False):
    
    return self.siblings_iter_class(self,copy,following)
    
self_and_siblings_iter = siblings_iter
    
def following_siblings_iter (self,copy=False):
    
    return self.siblings_iter_class(self,copy,following=True)
#@nonl
#@+node:ekr.20040812154341.80:__init__ & __iter__
def __init__(self,p,copy,following):
    
    # We always include p, even if following is True.
    
    if following:
        self.first = p.copy()
    else:
        p = p.copy()
        while p.hasBack():
            p.moveToBack()
        self.first = p

    self.p = None
    self.copy = copy

def __iter__(self):
    
    return self

#@-node:ekr.20040812154341.80:__init__ & __iter__
#@+node:ekr.20040812154341.81:next
def next(self):
    
    if self.first:
        self.p = self.first
        self.first = None

    elif self.p:
        self.p.moveToNext()

    if self.p:
        if self.copy: return self.p.copy()
        else:         return self.p
    else: raise StopIteration
#@nonl
#@-node:ekr.20040812154341.81:next
#@-node:ekr.20040812154341.79:p.siblings_iter
#@-node:ekr.20040812154341.64:p.Iterators (Can't use yield in pyrex)
#@-node:ekr.20040812170616:Pyrex versions of key classes
#@+node:ekr.20040803100035:Run Pychecker
#@+node:ekr.20031218072017.2606:<< Import pychecker >>
@color

# See pycheckrc file in leoDist.leo for a list of erroneous warnings to be suppressed.

if 0: # Set to 1 for lint-like testing.
    try:
        import pychecker.checker
        # This works.  We may want to set options here...
        # from pychecker import Config 
        print ; print "Warning: pychecker.checker running..." ; print
    except:
        pass
#@nonl
#@-node:ekr.20031218072017.2606:<< Import pychecker >>
#@-node:ekr.20040803100035:Run Pychecker
#@+node:ekr.20031218072017.984:Unfinished projects
#@+node:ekr.20031218072017.985:(Button class)
@ignore
@color
#@nonl
#@+node:ekr.20031218072017.986:Notes to Edward
@nocolor

I have a base window class that I use for all my TK stuff which handles, especially, focus issues between windows.  I have not looked at ButtonQ to see to what extent it depends upon WindowQ, the base class.

Anyway, this might be usefull as-is.  If not, let me know.
#@nonl
#@-node:ekr.20031218072017.986:Notes to Edward
#@+node:ekr.20031218072017.987:@file ButtonQ.py
@ Setup a button so it is handled with a keyboard shortcut like MSWindows tools.  See if there is an ampersand in the string.  If so, we want to underline that character and make that character a hot key for this button.
@c

from Tkinter import *
from string import *

def ButtonQ (master, **kw):
	b = ButtonQ_ (master, kw)
	return b.button

class ButtonQ_ (Button):
@others

## test
"""
from tkMessageBox import *
def buttonqcommand ():
	showinfo ("Title", "Button was pushed")

root = Tk ()
frame = Frame (root)
b = ButtonQ (frame, text = "&Push Me", command = buttonqcommand)
b.grid ()
frame.grid (pady = 15)
"""
#@+node:ekr.20031218072017.988:__init__
def __init__(self, master, kw = {}):

	<< get inputs to this method >>
	<< see if there is an ampersand in the string. >>
	<< make the button with the correct text >>
	<< bind the button >>

	# make the button available to the caller
	self.button = b
#@nonl
#@+node:ekr.20031218072017.989:<< get inputs to this method >>
_master = master
_text = kw['text']
_command = kw['command']
#@-node:ekr.20031218072017.989:<< get inputs to this method >>
#@+node:ekr.20031218072017.990:<< see if there is an ampersand in the string. >>
length = len (_text)
index = 0
text3 = _text

# Show there is no hot key yet
IsHot = 0
HotKey = None

while index < length:
	if _text [index] == '&':
		<< handle an ampersand >>
	index = += 1
#@nonl
#@+node:ekr.20031218072017.991:<< handle an ampersand >>
# if the word ends in an ampersand, we ignore it, and there is no hot key
if index == length - 1:
	break

# we have found a good hot key.  Remove the ampersand.
text1 = _text [0 : index]
text2 = _text [(index + 1) : 10000]
text3 = text1 + text2
IsHot = 1
HotKey = _text [index + 1]
break
#@nonl
#@-node:ekr.20031218072017.991:<< handle an ampersand >>
#@-node:ekr.20031218072017.990:<< see if there is an ampersand in the string. >>
#@+node:ekr.20031218072017.992:<< make the button with the correct text >>
if IsHot:
	kw['text'] = text3
	kw['underline'] = index

b = Button (master, kw)
#@-node:ekr.20031218072017.992:<< make the button with the correct text >>
#@+node:ekr.20031218072017.993:<< bind the button >>
@ If there is a hot key, bind it to the window that owns the button.
Use the alt of both the lower case and upper case of the letter.
@c

if IsHot:
	HotKey = lower(HotKey)
	self.HotKey = HotKey
	s = angleBrackets("Alt-" + HotKey + s)
	_master.master.bind (s, self.callback)

	HotKey = upper(HotKey)
	s = angleBrackets("Alt-" + HotKey + s)
	_master.master.bind (s, self.callback)


#@-node:ekr.20031218072017.993:<< bind the button >>
#@-node:ekr.20031218072017.988:__init__
#@+node:ekr.20031218072017.994:callback
# The hot key has been hit.  Call the button's command.

def callback (self, event):

	self.button.invoke ()
#@-node:ekr.20031218072017.994:callback
#@-node:ekr.20031218072017.987:@file ButtonQ.py
#@-node:ekr.20031218072017.985:(Button class)
#@+node:ekr.20031218072017.995:(Incremental update of screen)
@ignore
@nocolor

To enable incremental allocation of Tk widgets during redraws, set self.allocateOnlyVisibleNodes = True in tree.__init__.

To do:
	
- We might switch to a line-oriented scheme.
	- This might simplify the code and make the code more useful to users.
	- Conceivably this scheme might eliminate the need for the auto-scroll in the redraw code,
	and that might make a single-pass redraw scheme possible.

- The last line isn't always completely visible: this is clearly a bug.

@color
#@nonl
#@+node:ekr.20031218072017.996:From Frame class
#@+node:ekr.20031218072017.997:<< create the tree canvas >>
scrolls = config.getBoolWindowPref('outline_pane_scrolls_horizontally')
scrolls = choose(scrolls,1,0)

self.canvas = canvas = Tk.Canvas(split2Pane1,name="canvas",
	bd=0,bg="white",relief="flat")
	
self.setTreeColorsFromConfig()

self.treeBar = treeBar = Tk.Scrollbar(split2Pane1,name="treeBar")

# Bind mouse wheel event to canvas
if sys.platform != "win32": # Works on 98, crashes on XP.
	self.canvas.bind("<MouseWheel>", self.OnMouseWheel)
	
canvas['yscrollcommand'] = self.setCallback
treeBar['command']     = self.yviewCallback

treeBar.pack(side="right", fill="y")
if scrolls: 
	treeXBar = Tk.Scrollbar( 
		split2Pane1,name='treeXBar',orient="horizontal") 
	canvas['xscrollcommand'] = treeXBar.set 
	treeXBar['command'] = canvas.xview 
	treeXBar.pack(side="bottom", fill="x")

canvas.pack(expand=1,fill="both")
#@nonl
#@-node:ekr.20031218072017.997:<< create the tree canvas >>
#@+node:ekr.20031218072017.998:Scrolling callbacks (frame)
def setCallback (self,*args,**keys):
    
    """Callback to adjust the scrollbar.
    
    Args is a tuple of two floats describing the fraction of the visible area."""

    # g.trace(self.tree.redrawCount,args)

    apply(self.treeBar.set,args,keys)

    if self.tree.allocateOnlyVisibleNodes:
        self.tree.setVisibleArea(args)
        
def yviewCallback (self,*args,**keys):
    
    """Tell the canvas to scroll"""
    
    # g.trace(vyiewCallback",args,keys)

    if self.tree.allocateOnlyVisibleNodes:
        self.tree.allocateNodesBeforeScrolling(args)

    apply(self.canvas.yview,args,keys)
#@nonl
#@-node:ekr.20031218072017.998:Scrolling callbacks (frame)
#@-node:ekr.20031218072017.996:From Frame class
#@-node:ekr.20031218072017.995:(Incremental update of screen)
#@+node:ekr.20031218072017.1032:(Syntax coloring a la jEdit) (do not delete)
@ To do:

- ** Use a general purpose XML parser to parse the jEdit mode files.
- ** Do incremental syntax coloring
	- Create lines table.
	- Initialize lines table when select new node.
- Use strings for states.
- Define colorizeLine method.
- Create self.state ivar
- Use a keyword lead-in table.
	- Use this for latex keywords and Leo keywords.
	- Add ignore-case ivar.
- Define @language xml.
- Defined syntax coloring for all jEdit token types:
	KEYWORD1,KEYWORD2,KEYWORD3,LABEL,LITERAL1,LITERAL2,MARKUP,OPERATOR
	Where do Leo keywords fit in?

#@+node:ekr.20031218072017.1033:Ideas for dynamic code
@ignore

@ The following gives the general idea.

The self.known_languages dict contains all the data structures for a particular language.  This dict is created dynamically.  Each entry is a tuple of other info:
	"c"      : (dict1,...,dictM,list1,...,listN,etc.),
	"python" : (dict1,...,dictM,list1,...,listN,etc.),
These tuples, i.e., all the data structures, are created by the initialization routine.

@c
data = self.known_languages.get(self.language)
if not data:
	# Create all the data structures for the language.
	data = parse_xml_file(self.language) 
	if data: self.known_languages[language] = data
if data:
	self.dict1,...,self.listN=data

# The subsidiary data structures will be created from the xml files.
# The exact data structures used depends on the format of the xml files.

self.reserved_words1 = {} # Main language keywords: reserved words.
self.reserved_words2 = {} # Functions etc. to be colored separately.
self.keyword_chars1 = {} # Characters that appear as the first character of a keyword.
self.keyword_chars2 = {} # Characters that appear as second or following characters of a keyword.
self.string_start1,self.string.start2 # Opening characters of strings.

# The code might scan for keywords as follows:
n = len(s)
if i < n and self.keywords1.get(s[i]):
	j = i ; i += 1
	while i < n and self.keywords2.get(s[i]):
		i += 1
	word = s[j:i]
	if self.reserved_words1.get(word):
		# colorize the reserved word
	elif self.reserved_words2.get(word):
		# colorize the reserved word
#@-node:ekr.20031218072017.1033:Ideas for dynamic code
#@+node:ekr.20031218072017.1034:New data structures
special_keywords_dict = {
	'\\' : (latex_keywords_dict,"keyword"),
	'@' :  (leo_keywords_dict,"leoKeword") }
	
if << ch is alphabetic >>:
	word = scan_c_word(s,i)
	kind = keywords_dict.get(word)
	if kind:
		<< colorize word using kind >>
else:
	data = special_keywords_dict.get(ch)
	if data:
		dict,kind = data
		word = scan_c_word(s,i+1)
		key = d.get(word)
		if key:
			<< colorize ch and word using kind >>
#@nonl
#@-node:ekr.20031218072017.1034:New data structures
#@+node:ekr.20031218072017.1049:jEdit docs
@nocolor

#@+node:ekr.20031218072017.1050:The Preamble and MODE tag
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.20031218072017.1050:The Preamble and MODE tag
#@+node:ekr.20031218072017.1051:The PROPS Tag (Leo could ignore these)
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. In addition, the following mode properties have no buffer-local equivalent: 

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. 

doubleBracketIndent - If a line matches the indentPrevLine regular expression and the next line contains an opening bracket, a level of indent will not be added to the next line, unless this property is set to "True". For example, with this property set to "False", Java code will be indented like so: 

while(objects.hasMoreElements())
{
        ((Drawable)objects.nextElement()).draw();
} 

On the other hand, settings this property to "True" will give the following result: 

while(objects.hasMoreElements())
        {
                ((Drawable)objects.nextElement()).draw();
        } 

indentCloseBrackets - A list of characters (usually brackets) that subtract indent from the current line. For example, in Java mode this property is set to "}".

indentOpenBrackets - A list of characters (usually brackets) that add indent to the next line. For example, in Java mode this property is set to "{".

indentPrevLine - When indenting a line, jEdit checks if the previous line matches the regular expression stored in this property. If it does, a level of indent is added. For example, in Java mode this regular expression matches language constructs such as "if", "else", "while", etc.

Here is the complete <PROPS> tag for Java mode: 

<PROPS>
    <PROPERTY NAME="indentOpenBrackets" VALUE="{" />
    <PROPERTY NAME="indentCloseBrackets" VALUE="}" />
    <PROPERTY NAME="indentPrevLine" VALUE="\s*(((if|while)
        \s*\(|else|case|default)[^;]*|for\s*\(.*)" />
    <PROPERTY NAME="doubleBracketIndent" VALUE="false" />
    <PROPERTY NAME="commentStart" VALUE="/*" />
    <PROPERTY NAME="commentEnd" VALUE="*/" />
    <PROPERTY NAME="blockComment" VALUE="//" />
    <PROPERTY NAME="wordBreakChars" VALUE=",+-=<>/?^&*" />
</PROPS> 
#@-node:ekr.20031218072017.1051:The PROPS Tag (Leo could ignore these)
#@+node:ekr.20031218072017.1052:The RULES Tag
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 Rule". 

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. 

HIGHLIGHT_DIGITS - if set to TRUE, digits (0-9, as well as hexadecimal literals prefixed with "0x") will be highlighted with the DIGIT token type. Default is FALSE. 

IGNORE_CASE - if set to FALSE, matches will be case sensitive. Otherwise, case will not matter. Default is TRUE. 

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. 

Here is an example RULES tag: 

<RULES IGNORE_CASE="FALSE" HIGHLIGHT_DIGITS="TRUE">
    ... parser rules go here ...
</RULES> 

Rule Ordering Requirements

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. 

This is best demonstrated with an example. The following is incorrect rule ordering: 

<SPAN TYPE="MARKUP">
    <BEGIN>[</BEGIN>
    <END>]</END>
</SPAN>

<SPAN TYPE="KEYWORD1">
    <BEGIN>[!</BEGIN>
    <END>]</END>
</SPAN> 

If you write the above in a rule set, any occurrence of "[" (even things like "[!DEFINE", etc) will be highlighted using the first rule, because it will be the first to match. This is most likely not the intended behavior. 

The problem can be solved by placing the more specific rule before the general one: 

<SPAN TYPE="KEYWORD1">
    <BEGIN>[!</BEGIN>
    <END>]</END>
</SPAN>

<SPAN TYPE="MARKUP">
    <BEGIN>[</BEGIN>
    <END>]</END>
</SPAN> 

Now, if the buffer contains the text "[!SPECIAL]", the rules will be checked in order, and the first rule will be the first to match. However, if you write "[FOO]", it will be highlighted using the second rule, which is exactly what you would expect. 

Per-Ruleset Properties

The PROPS tag (described in the section called "The PROPS Tag") can also be placed inside the RULES tag to define ruleset-specific properties. Only the following properties can be set on a per-ruleset basis: 

commentEnd - the comment end string. 

commentStart - the comment start string. 

lineComment - the line comment string. 

These properties are used by the commenting commands to implement context-sensitive comments; see the section called "Commenting Out Code". 

The TERMINATE Rule

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. 

The SPAN Rule

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

EXCLUDE_MATCH - If set to TRUE, the start and end sequences will not be highlighted, only the text between them will

NO_LINE_BREAK - If set to TRUE, the span will be highlighted with the INVALID token type if it spans more than one line

NO_WORD_BREAK - If set to TRUE, the span will be highlighted with the INVALID token type if it includes whitespace

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

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> 

Tip
The <END> tag is optional. If it is not specified, any occurrence of the start string will cause the remainder of the buffer to be highlighted with this rule. 

This can be very useful when combined with delegation. 

The EOL_SPAN Rule
An EOL_SPAN is similar to a SPAN except that highlighting stops at the end of the line, not after the end sequence is found. 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

EXCLUDE_MATCH - If set to TRUE, the start sequence will not be highlighted, only the text after it will

Here is an EOL_SPAN that highlights C++ comments: 

<EOL_SPAN TYPE="COMMENT1">//</EOL_SPAN> 

The MARK_PREVIOUS Rule
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. 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 text will only be highlighted if it occurs at the beginning of the line

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_LINE_START="TRUE"
    EXCLUDE_MATCH="TRUE">:</MARK_PREVIOUS> 

The MARK_FOLLOWING Rule
The MARK_FOLLOWING rule 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 text will only be highlighted if the start sequence occurs at the beginning of a line

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> 

The SEQ Rule
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: 

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

The following rules highlight a few Java operators: 

<SEQ TYPE="OPERATOR">+</SEQ>
<SEQ TYPE="OPERATOR">-</SEQ>
<SEQ TYPE="OPERATOR">*</SEQ>
<SEQ TYPE="OPERATOR">/</SEQ> 

The KEYWORDS Rule

There can only be one KEYWORDS tag per ruleset. The KEYWORDS rule defines keywords to highlight. Keywords are similar to SEQs, except that SEQs match anywhere in the text, whereas keywords only match whole words. 

The KEYWORDS tag does not define any attributes. 

Each child element of the KEYWORDS tag should be named after the desired token type, with the keyword text between the start and end tags. For example, the following rule highlights the most common Java keywords: 

<KEYWORDS IGNORE_CASE="FALSE">
   <KEYWORD1>if</KEYWORD1>
   <KEYWORD1>else</KEYWORD1>
   <KEYWORD3>int</KEYWORD3>
   <KEYWORD3>void</KEYWORD3>
</KEYWORDS> 

Token Types

Parser rules can highlight tokens using any of the following token types: 

NULL - no special highlighting is performed on tokens of type NULL 

COMMENT1 

COMMENT2 

FUNCTION 

INVALID - tokens of this type are automatically added if a NO_WORD_BREAK or NO_LINE_BREAK SPAN spans more than one word or line, respectively. 

KEYWORD1 

KEYWORD2 

KEYWORD3 

LABEL 

LITERAL1 

LITERAL2 

MARKUP 

OPERATOR 
#@-node:ekr.20031218072017.1052:The RULES Tag
#@-node:ekr.20031218072017.1049:jEdit docs
#@-node:ekr.20031218072017.1032:(Syntax coloring a la jEdit) (do not delete)
#@+node:ekr.20031218072017.1658:(Using xml parser)
import leoGlobals
import os,leoNodes
from xml.sax import saxutils, make_parser
from xml.sax.xmlreader import InputSource

path = os.path.join(app().loadDir,"../","test","test2.leo")
path = os.path.join(app().loadDir,"LeoPy.leo")
path = os.path.normpath(path)

verbose = False
tnodes = vnodes = 0

def clean(s): return toEncodedString(s,"ascii")

class trace_parse(saxutils.XMLGenerator):
@others
	
try:
	f = None
	try:
		print path
		if 1:
			source = f = open(path)
		else: # not needed, and it works.
			source = InputSource(path)
			source.setEncoding(app().tkEncoding) # Not needed.
		parser = make_parser()
		h = trace_parse()
		parser.setContentHandler(h)
		parser.parse(source)
		print "vnodes,tnodes:",`vnodes`,`tnodes`
	except: g.es_exception()
finally:
	if f: f.close()
#@nonl
#@+node:ekr.20031218072017.1659:characters
def characters(self,content):

	content = content.replace('\r','')

	if verbose and content.strip():
		print clean(content)
#@-node:ekr.20031218072017.1659:characters
#@+node:ekr.20031218072017.1660:endDocument
def endDocument(self):
	trace()


#@-node:ekr.20031218072017.1660:endDocument
#@+node:ekr.20031218072017.1661:endElement
def endElement(self,name):
	if verbose: print '</' + clean(name).strip() + '>'
#@-node:ekr.20031218072017.1661:endElement
#@+node:ekr.20031218072017.1662:other methods
def ignorableWhitespace(self):
	trace()

def processingInstruction (self,target,data):
	trace()

def skippedEntity(self,name):
	trace(name)

def startElementNS(self,name,qname,attrs):
	trace(name)

def endElementNS(self,name,qname):
	trace(name)
#@-node:ekr.20031218072017.1662:other methods
#@+node:ekr.20031218072017.1663:startDocument
def startDocument(self):

	if verbose:
		print ; print ; print '*' * 30 + " dump " + '*' * 30 ; print
	trace()
#@-node:ekr.20031218072017.1663:startDocument
#@+node:ekr.20031218072017.1664:startElement
def startElement(self,name,atts):
	global vnodes,tnodes
	if verbose: print '<' + clean(name).strip() + '>',
	if name == "v":
		vnodes += 1
		v = leoNodes.vnode(top(),leoNodes.tnode())
	elif name == "t":
		tnodes += 1
		t = leoNodes.tnode()
#@nonl
#@-node:ekr.20031218072017.1664:startElement
#@-node:ekr.20031218072017.1658:(Using xml parser)
#@-node:ekr.20031218072017.984:Unfinished projects
#@+node:ekr.20040809102904:4.2 rc1 projects
#@+node:ekr.20040809095716:(Restored the "iconclick1/2" hooks)
#@+node:ekr.20040803072955.81:onIconBoxClick
def onIconBoxClick (self,event):
    
    c = self.c ; gui = g.app.gui
    tree = self ; canvas = tree.canvas
    
    if self.trace and self.verbose: g.trace()
    
    p = self.eventToPosition(event)
    if not p: return
    
    if not g.doHook("iconclick1",c=c,p=p,event=event):
        if event:
            self.onDrag(event)
        tree.select(p)
        g.app.findFrame.handleUserClick(p) # 4/3/04
        g.doHook("iconclick2",c=c,p=p,event=event)
        
    return "break" # disable expanded box handling.
#@nonl
#@-node:ekr.20040803072955.81:onIconBoxClick
#@-node:ekr.20040809095716:(Restored the "iconclick1/2" hooks)
#@+node:ekr.20040830210600:(Made sure a proper message is given with invalid versions of Python)
#@+node:ekr.20040831063427:Report
@killcolor

I'm a new user and just installed 4.2b3 on a Win2ksp4 box with Python 2.2 and Tcl/Tk 8.4.6.1

Leo refuses to start. This is what I get when using the Python repl:

>>> import leo
>>> leo.run()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "leo.py", line 45, in run
if not isValidPython(): return
File "leo.py", line 118, in isValidPython
import leoGlobals as g
File "k:\apps\Leo\src\leoGlobals.py", line 51, in ?
def createTopologyList (c=None,root=None,useHeadlines=False):
NameError: name 'False' is not defined

> NameError: name 'False' is not defined

Thanks for this report.

Leo has code that is intended to warn if you aren't using the proper version of Python.  Apparently this code isn't getting a chance to execute before the traceback is generated.  This is a bug.  Thanks again.

Edward
#@nonl
#@-node:ekr.20040831063427:Report
#@+node:ekr.20031218072017.1936:isValidPython
def isValidPython():

    message = """\
Leo requires Python 2.2.1 or higher.
You may download Python from http://python.org/download/
"""
    
    try:
        # This will fail if True/False are not defined.
        import leoGlobals as g
    except:
        print message
        return 0
    try:
        ok = g.CheckVersion(sys.version, "2.2.1")
        if not ok:
            g.app.gui.runAskOkDialog("Python version error",message=message,text="Exit")
        return ok
    except:
        print "exception getting Python version"
        import traceback ; traceback.print_exc()
        return False
#@nonl
#@-node:ekr.20031218072017.1936:isValidPython
#@-node:ekr.20040830210600:(Made sure a proper message is given with invalid versions of Python)
#@+node:ekr.20040831093934:(Fixed problem with tabs on MacOs)
#@+node:ekr.20040831093934.1:Report
@killcolor

With TclTkAqua on the Mac (but *not* with tcltk installed via Fink), the tab
problem doesn't exist there), the tab, return, and backspace keys do not generate
ASCII codes, i.e. event.char is an empty string. So when I press the tab key,
Tk inserts a tab character and Leo isn't even aware of it.
#@nonl
#@-node:ekr.20040831093934.1:Report
#@+node:ekr.20031218072017.1330:onBodyKey
def onBodyKey (self,event):
    
    """Handle any key press event in the body pane."""

    c = self.c ; ch = event.char
    
    # This translation is needed on MacOS.
    if ch == '':
        d = {'Return':'\r', 'Tab':'\t', 'BackSpace':chr(8)}
        ch = d.get(event.keysym,'')

    oldSel = c.frame.body.getTextSelection()
    
    p = c.currentPosition()

    if 0: # won't work when menu keys are bound.
        self.handleStatusLineKey(event)
        
    # We must execute this even if len(ch) > 0 to delete spurious trailing newlines.
    self.c.frame.bodyCtrl.after_idle(self.idle_body_key,p,oldSel,"Typing",ch)
#@nonl
#@+node:ekr.20040105223536:handleStatusLineKey
def handleStatusLineKey (self,event):
    
    c = self.c ; frame = c.frame
    ch = event.char ; keysym = event.keysym
    keycode = event.keycode ; state = event.state

    if 1: # ch and len(ch)>0:
        << trace the key event >>

    try:
        status = self.keyStatus
    except:
        status = [] ; frame.clearStatusLine()
    
    for sym,name in (
        ("Alt_L","Alt"),("Alt_R","Alt"),
        ("Control_L","Control"),("Control_R","Control"),
        ("Escape","Esc"),
        ("Shift_L","Shift"), ("Shift_R","Shift")):
        if keysym == sym:
            if name not in status:
                status.append(name)
                frame.putStatusLine(name + ' ')
            break
    else:
        status = [] ; frame.clearStatusLine()

    self.keyStatus = status
#@nonl
#@+node:ekr.20040105223536.1:<< trace the key event >>
try:    self.keyCount += 1
except: self.keyCount  = 1

printable = g.choose(ch == keysym and state < 4,"printable","")

print "%4d %s %d %s %x %s" % (
    self.keyCount,repr(ch),keycode,keysym,state,printable)
#@nonl
#@-node:ekr.20040105223536.1:<< trace the key event >>
#@-node:ekr.20040105223536:handleStatusLineKey
#@-node:ekr.20031218072017.1330:onBodyKey
#@-node:ekr.20040831093934:(Fixed problem with tabs on MacOs)
#@+node:ekr.20040829101055:(Fixed Import Derived Files command)
#@+node:ekr.20040831095613:Notes
@killcolor

Leo gave an error for every character of the file name!

This method got overlooked when runOpenFileDialog got changed recently.

Added unit test to test.leo.
#@nonl
#@-node:ekr.20040831095613:Notes
#@+node:ekr.20031218072017.1809:importDerivedFile
def importDerivedFile (self):
    
    """Create a new outline from a 4.0 derived file."""
    
    c = self ; frame = c.frame ; v = c.currentVnode()
    
    types = [
        ("All files","*"),
        ("C/C++ files","*.c"),
        ("C/C++ files","*.cpp"),
        ("C/C++ files","*.h"),
        ("C/C++ files","*.hpp"),
        ("Java files","*.java"),
        ("Pascal files","*.pas"),
        ("Python files","*.py") ]
    
    names = g.app.gui.runOpenFileDialog(
        title="Import Derived File",
        filetypes=types,
        defaultextension=".py",
        multiple=True)

    if names:
        c.importCommands.importDerivedFiles(v,names)
#@nonl
#@-node:ekr.20031218072017.1809:importDerivedFile
#@-node:ekr.20040829101055:(Fixed Import Derived Files command)
#@+node:ekr.20040831104303:(Fixed parsing of minimal <preferences> tag
#@+node:ekr.20040831104303.1:Report
@killcolor

http://sourceforge.net/forum/message.php?msg_id=2720802
By: billp9619

This  file testmini.leo

<?xml version="1.0" encoding="UTF-8"?>
<leo_file>
<leo_header/>
<globals/>
<preferences allow_rich_text="0"/>
<find_panel_settings/>
<vnodes/>
<tnodes/>
</leo_file>

Gives error message as follows.

Leo Log Window...
Leo 4.2 beta 3, build  1.152 , August 10, 2004
Python 2.3.4, Tk 8.4.3, win32

File encoding: UTF-8
Traceback (most recent call last):
  File "C:\Program Files\Leo\src\leoFileCommands.py", line 610, in getLeoFile
    self.getPrefs()
  File "C:\Program Files\Leo\src\leoFileCommands.py", line 814, in getPrefs
    self.getUnknownTag()
  File "C:\Program Files\Leo\src\leoFileCommands.py", line 371, in
getUnknownTag
    tag = self.getStringToTag('=')
  File "C:\Program Files\Leo\src\leoFileCommands.py", line 349, in
getStringToTag
    raise BadLeoFile("expecting string terminated by " + tag)
BadLeoFile: Bad Leo File:expecting string terminated by =
C:\Program Files\Leo\testmini.leo is not a valid Leo file: Bad Leo File:expecting
string terminated by =


A copy of LeoDocs.leo with the <preferences allow_rich_text="0"/> tag in it
gave this error message:

File encoding: UTF-8
Traceback (most recent call last):
  File "C:\Program Files\Leo\src\leoFileCommands.py", line 610, in getLeoFile
    self.getPrefs()
  File "C:\Program Files\Leo\src\leoFileCommands.py", line 827, in getPrefs
    self.getTag("</preferences>")
  File "C:\Program Files\Leo\src\leoFileCommands.py", line 365, in getTag
    raise BadLeoFile("expecting" + tag)
BadLeoFile: Bad Leo File:expecting</preferences>
C:\Program Files\Leo\leodocsTESTCOPY.leo is not a valid Leo file: Bad Leo
File:expecting</preferences>

regards,

bill p
#@nonl
#@-node:ekr.20040831104303.1:Report
#@+node:ekr.20031218072017.2062:getPrefs
def getPrefs (self):

    c = self.c ; config = g.app.config
    
    if self.getOpenTag("<preferences"):
        return # <preferences/> seeen

    table = (
        ("allow_rich_text",None,None), # Ignored.
        ("tab_width","tab_width",self.getLong),
        ("page_width","page_width",self.getLong),
        ("tangle_bat","tangle_batch_flag",self.getBool),
        ("untangle_bat","untangle_batch_flag",self.getBool),
        ("output_doc_chunks","output_doc_flag",self.getBool),
        ("noweb_flag",None,None), # Ignored.
        ("extended_noweb_flag",None,None), # Ignored.
        ("defaultTargetLanguage","target_language",self.getTargetLanguage),
        ("use_header_flag","use_header_flag",self.getBool))
    
    done = False
    while 1:
        found = False
        for tag,var,f in table:
            if self.matchTag("%s=" % tag):
                if var:
                    self.getDquote() ; val = f() ; self.getDquote()
                    setattr(c,var,val)
                else:
                    self.getDqString()
                found = True ; break
        if not found:
            if self.matchTag("/>"):
                done = True ; break
            if self.matchTag(">"):
                break
            else: # New in 4.1: ignore all other tags.
                self.getUnknownTag()

    if not done: # 8/31/04
        while 1:
            if self.matchTag("<defaultDirectory>"):
                # New in version 0.16.
                c.tangle_directory = self.getEscapedString()
                self.getTag("</defaultDirectory>")
                if not g.os_path_exists(c.tangle_directory):
                    g.es("default tangle directory not found:" + c.tangle_directory)
            elif self.matchTag("<TSyntaxMemo_options>"):
                self.getEscapedString() # ignored
                self.getTag("</TSyntaxMemo_options>")
            else: break
        self.getTag("</preferences>")

    # Override .leo file's preferences if settings are in leoConfig.txt.
    if config.configsExist:
        config.setCommandsIvars(c)
#@nonl
#@+node:ekr.20031218072017.2063:getTargetLanguage
def getTargetLanguage (self):
    
    # Must match longer tags before short prefixes.
    for name in g.app.language_delims_dict.keys():
        if self.matchTagWordIgnoringCase(name):
            language = name.replace("/","")
            # self.getDquote()
            return language
            
    return "c" # default
#@nonl
#@-node:ekr.20031218072017.2063:getTargetLanguage
#@-node:ekr.20031218072017.2062:getPrefs
#@-node:ekr.20040831104303:(Fixed parsing of minimal <preferences> tag
#@+node:ekr.20040831110923:(Allow longer headlines for to support long url's)
#@+node:ekr.20040831111203:Report
@killcolor

From Dan Winkler:

On a couple of occasions, I tried to paste in a long @url node and got 
this message:

	Truncating headline to 250 characters

I wonder if the 250 character limit on headline length should be 
rethought in light of the @url directive and long web addresses.
#@nonl
#@-node:ekr.20040831111203:Report
#@+node:ekr.20040803072955.91:idle_head_key
def idle_head_key (self,p,ch=None):
    
    """Update headline text at idle time."""
    
    c = self.c

    if not p or not p.isCurrentPosition():
        return "break"
        
    edit_text = self.edit_text(p)
    index = edit_text.index("insert")

    if g.doHook("headkey1",c=c,p=p,ch=ch):
        return "break" # The hook claims to have handled the event.
        
    << set head to vnode text >>
    done = ch in ('\r','\n')
    if done:
        << set the widget text to head >>
    << set s to the widget text >>
    changed = s != head
    if changed:
        c.undoer.setUndoParams("Change Headline",p,newText=s,oldText=head)
        << update p >>
    if done or changed:
        << reconfigure p and all nodes joined to p >>
        << update the screen >>

    g.doHook("headkey2",c=c,p=p,ch=ch)
    return "break"
#@nonl
#@+node:ekr.20040803072955.92:<< set head to vnode text >>
head = p.headString()
if head == None:
    head = u""
head = g.toUnicode(head,"utf-8")
#@nonl
#@-node:ekr.20040803072955.92:<< set head to vnode text >>
#@+node:ekr.20040803072955.93:<< set the widget text to head >>
self.setText(edit_text,head,tag="idle_head_key")
edit_text.mark_set("insert",index)
#@nonl
#@-node:ekr.20040803072955.93:<< set the widget text to head >>
#@+node:ekr.20040803072955.94:<< set s to the widget text >>
s = edit_text.get("1.0","end")

# Don't truncate if the user is hitting return.
# That should just end editing.
if 1:
    # Truncate headline text to workaround Tk problems...
    # Another kludge: remove one or two trailing newlines before warning of truncation.
    if s and s[-1] == '\n': s = s[:-1]
    if s and s[-1] == '\n': s = s[:-1]
    i = s.find('\n')
    if i > -1:
        # g.trace(i,len(s),repr(s))
        g.es("Truncating headline to one line",color="blue")
        s = s[:i]
    limit = 1000
    if len(s) > limit:
        g.es("Truncating headline to %d characters" % (limit),color="blue")
        s = s[:limit]

s = g.toUnicode(s,g.app.tkEncoding)

if not s:
    s = u""
    
if 0: # 6/10/04: No longer needed.  This was stressing Tk needlessly.
    s = s.replace('\n','')
    s = s.replace('\r','')
#@nonl
#@-node:ekr.20040803072955.94:<< set s to the widget text >>
#@+node:ekr.20040803072955.95:<< update p >>
c.beginUpdate()
if 1: # update...
    # Update changed bit.
    if not c.changed:
        c.setChanged(True)
    # Update all dirty bits.
    # Bug fix 8/2/04: must call p.setDirty even if p is dirty!
    p.setDirty()
    # Update p.
    p.initHeadString(s)
    self.setText(edit_text,s,tag="idle_head_key2")
    edit_text.mark_set("insert",index)
c.endUpdate(False) # do not redraw now.
#@nonl
#@-node:ekr.20040803072955.95:<< update p >>
#@+node:ekr.20040803072955.96:<< reconfigure p and all nodes joined to p >>
# Reconfigure p's headline.
if done:
    self.setDisabledLabelState(p)

edit_text.configure(width=self.headWidth(p))
#@nonl
#@-node:ekr.20040803072955.96:<< reconfigure p and all nodes joined to p >>
#@+node:ekr.20040803072955.97:<< update the screen >>
if done:
    # g.trace("done")
    c.beginUpdate()
    self.endEditLabel()
    c.endUpdate()

elif changed:
    # g.trace("changed")
    # Update p immediately.  Joined nodes are redrawn later by endEditLabel.
    # Redrawing the whole screen now messes up the cursor in the headline.
    self.drawIcon(p) # just redraw the icon.
#@nonl
#@-node:ekr.20040803072955.97:<< update the screen >>
#@-node:ekr.20040803072955.91:idle_head_key
#@-node:ekr.20040831110923:(Allow longer headlines for to support long url's)
#@+node:ekr.20040831120106:(Fixed read errors with @language html)
#@+node:ekr.20040831120106.1:Report
@killcolor

http://sourceforge.net/forum/message.php?msg_id=2714125

1) Create a node that contains the following.

@language html
@doc

Multi
line
HTML
comments

@c
<p>HTML code</p>

2) Save the leo file.
3) Re-open leo and you'll get a "Missing open block comment in readLastDocLine:"
error.

Leo 4.2 beta 3, build  1.152 , August 10, 2004
Python 2.3.4, Tk 8.4.4, linux2
#@nonl
#@-node:ekr.20040831120106.1:Report
#@+node:ekr.20031218072017.1753:readLastDocLine
def readLastDocLine (self,tag):
    
    """Read the @c line that terminates the doc part.
    tag is @doc or @."""
    
    at = self
    end = at.endSentinelComment
    start = at.startSentinelComment
    s = ''.join(at.docOut)
    
    # Remove the @doc or @space.  We'll add it back at the end.
    if g.match(s,0,tag):
        s = s[len(tag):]
    else:
        at.readError("Missing start of doc part")
        return

    if end:
        # 9/3/04: Remove leading newline.
        if s[0] == '\n': s = s[1:]
        # Remove opening block delim.
        if g.match(s,0,start):
            s = s[len(start):]
        else:
            at.readError("Missing open block comment")
            g.trace(s)
            return
        # Remove trailing newline.
        if s[-1] == '\n': s = s[:-1]
        # Remove closing block delim.
        if s[-len(end):] == end:
            s = s[:-len(end)]
        else:
            at.readError("Missing close block comment")
            return

    at.out.append(tag + s)
    at.docOut = []
    
#@nonl
#@-node:ekr.20031218072017.1753:readLastDocLine
#@-node:ekr.20040831120106:(Fixed read errors with @language html)
#@+node:ekr.20040831113849:(Removed spurious "Warning: updating changed text" message)
#@+node:ekr.20040831121549.1:Report
@killcolor

http://sourceforge.net/forum/message.php?msg_id=2735294
By: lmcgign

If I create an @file or @thin node, clone that node, put an
'@first <something>' directive in it, save all changes and exit, and *then*
open up the file in Leo again, Leo indicates there have been changes (with the
'*' in the title bar) to the Leo file as soon as it starts up.

If the node is *not* cloned, or if the node does *not* contain a *non empty*
@first directive, this behavior does not appear.

I'm running Leo 4.2 beta 3, build  1.152,
Python 2.3.4, Tk 8.4.3, win32.

-----------

Small correction:

If the node is an @file one the behavior appears, even if the node has *not*
been cloned.

For an @thin node, though, the node needs to be cloned for the behavior
to appear.

Leo's console window indicates a Warning line when the behavior occurs:

reading: <leo_filename>
reading: @file gratis.py
Warning: updating changed text   <--- 
leoConfig.txt encoding: utf-8

-Martin
#@nonl
#@-node:ekr.20040831121549.1:Report
#@+node:ekr.20040904080309:Notes
@killcolor

- This happens in leoPlugins.leo at present.

- It happens with @file and @first, but not with @thin and @first.
#@nonl
#@-node:ekr.20040904080309:Notes
#@+node:ekr.20031218072017.2772:readEndNode (4.x)
def readEndNode (self,s,i,middle=False):
    
    """Handle end-of-node processing for @-others and @-ref sentinels."""

    at = self ; c = self.c
    
    # End raw mode.
    at.raw = False
    
    # Set the temporary body text.
    s = ''.join(at.out)
    s = g.toUnicode(s,g.app.tkEncoding) # 9/28/03

    if at.importing:
        at.t.bodyString = s
    elif middle: 
        pass # Middle sentinels never alter text.
    else:
        if hasattr(at.t,"tempBodyString") and s != at.t.tempBodyString:
            old = at.t.tempBodyString
        elif at.t.hasBody() and s != at.t.getBody():
            old = at.t.getBody()
        else:
            old = None
        # 9/4/04: Suppress this warning for the root: @first complicates matters.
        if old and not g.app.unitTesting and at.t != at.root.t:
            << indicate that the node has been changed >>
        at.t.tempBodyString = s

    # Indicate that the tnode has been set in the derived file.
    at.t.setVisited()

    # End the previous node sentinel.
    at.indent = at.indentStack.pop()
    at.out = at.outStack.pop()
    at.t = at.tStack.pop()
    if at.thinFile and not at.importing:
        at.lastThinNode = at.thinNodeStack.pop()

    at.popSentinelStack(endNode)
#@nonl
#@+node:ekr.20040904081433:<< indicate that the node has been changed >>
if at.perfectImportRoot:
    << bump at.correctedLines and tell about the correction >>
    # p.setMarked()
    at.t.bodyString = s # Just etting at.t.tempBodyString won't work here.
    at.t.setDirty() # Mark the node dirty.  Ancestors will be marked dirty later.
    at.c.setChanged(True)
else:
    if not at.updateWarningGiven:
        at.updateWarningGiven = True
        # print "***",at.t,at.root.t
        g.es("Warning: updating changed text in %s" %
            (at.root.headString()),color="blue")
    # g.es("old...\n%s\n" % old)
    # g.es("new...\n%s\n" % s)
    # Just set the dirty bit. Ancestors will be marked dirty later.
    at.t.setDirty()
    if 1: # We must avoid the full setChanged logic here!
        c.changed = True
    else: # Far too slow for mass changes.
        at.c.setChanged(True)
#@nonl
#@+node:ekr.20040717133944:<< bump at.correctedLines and tell about the correction >>
# Report the number of corrected nodes.
at.correctedLines += 1

found = False
for p in at.perfectImportRoot.self_and_subtree_iter():
    if p.v.t == at.t:
        found = True ; break

if found:
    if 0: # Not needed: we mark all corrected nodes.
        g.es("Correcting %s" % p.headString(),color="blue")
    if 0: # For debugging.
        print ; print '-' * 40
        print "old",len(old)
        for line in g.splitLines(old):
            #line = line.replace(' ','< >').replace('\t','<TAB>')
            print repr(str(line))
        print ; print '-' * 40
        print "new",len(s)
        for line in g.splitLines(s):
            #line = line.replace(' ','< >').replace('\t','<TAB>')
            print repr(str(line))
        print ; print '-' * 40
else:
    # This should never happen.
    g.es("Correcting hidden node: t=%s" % repr(at.t),color="red")
#@nonl
#@-node:ekr.20040717133944:<< bump at.correctedLines and tell about the correction >>
#@-node:ekr.20040904081433:<< indicate that the node has been changed >>
#@-node:ekr.20031218072017.2772:readEndNode (4.x)
#@-node:ekr.20040831113849:(Removed spurious "Warning: updating changed text" message)
#@+node:ekr.20040901061935:(Added option to disable left click logic in outline pane)
#@+node:ekr.20040906081644:Notes
@killcolor

http://sourceforge.net/forum/message.php?msg_id=2706676
By: nobody

Ive downloaded 4.2B3 and really really dont want to use the left click select/expand
anywhere Tree functionality( Ive never had a problem selecting the node I want
:) ).  I looked in the config file and didnt see an option for turning it off.
So do I need to hack the leo code to do this?

Option name: expanded_click_area
#@nonl
#@-node:ekr.20040906081644:Notes
#@+node:ekr.20040803072955.16:__init__
def __init__(self,c,frame,canvas):
    
    # Init the base class.
    leoFrame.leoTree.__init__(self,frame)

    # Objects associated with this tree.
    self.canvas = canvas
    
    << define drawing constants >>
    << old ivars >>
    << inject callbacks into the position class >>
    
    self.redrawing = False # Used only to disable traces.
    self.trace = False
    self.verbose = False
    self.useBindtags = True
    self.generation = 0
    self.dragging = False
    self.prevPositions = 0
    self.expanded_click_area = g.app.config.getBoolWindowPref("expanded_click_area")
    
    self.createPermanentBindings()
    self.setEditPosition(None) # Set positions returned by leoTree.editPosition()
    
    # Keys are id's, values are unchanging positions...
    self.ids = {}
    self.iconIds = {}

    # Lists of visible (in-use) widgets...
    self.visibleBoxes = []
    self.visibleClickBoxes = []
    self.visibleIcons = []
    self.visibleLines = []
    self.visibleText  = {} # Keys are vnodes, values are Tk.Text widgets
    self.visibleUserIcons = []

    # Lists of free, hidden widgets...
    self.freeBoxes = []
    self.freeClickBoxes = []
    self.freeIcons = []
    self.freeLines = []
    self.freeText = {} # Keys are vnodes, values are Tk.Text widgets
    self.freeUserIcons = []
#@nonl
#@+node:ekr.20040803072955.17:<< define drawing constants >>
self.box_padding = 5 # extra padding between box and icon
self.box_width = 9 + self.box_padding
self.icon_width = 20
self.text_indent = 4 # extra padding between icon and tex

self.hline_y = 7 # Vertical offset of horizontal line
self.root_left = 7 + self.box_width
self.root_top = 2

self.default_line_height = 17 + 2 # default if can't set line_height from font.
self.line_height = self.default_line_height
#@nonl
#@-node:ekr.20040803072955.17:<< define drawing constants >>
#@+node:ekr.20040803072955.18:<< old ivars >>
# Miscellaneous info.
self.iconimages = {} # Image cache set by getIconImage().
self.active = False # True if tree is active
self._editPosition = None # Returned by leoTree.editPosition()
self.lineyoffset = 0 # y offset for this headline.
self.disableRedraw = False # True: reschedule a redraw for later.
self.lastClickFrameId = None # id of last entered clickBox.
self.lastColoredText = None # last colored text widget.

# Set self.font and self.fontName.
self.setFontFromConfig()

# Drag and drop
self.drag_p = None
self.controlDrag = False # True: control was down when drag started.

# Keep track of popup menu so we can handle behavior better on Linux Context menu
self.popupMenu = None

# Incremental redraws:
self.allocateOnlyVisibleNodes = False # True: enable incremental redraws.
self.prevMoveToFrac = None
self.visibleArea = None
self.expandedVisibleArea = None

if self.allocateOnlyVisibleNodes:
    self.frame.bar1.bind("<B1-ButtonRelease>", self.redraw)
#@nonl
#@-node:ekr.20040803072955.18:<< old ivars >>
#@+node:ekr.20040803072955.19:<< inject callbacks into the position class >>
# The new code injects 3 callbacks for the colorizer.

if not leoTkinterTree.callbacksInjected: # Class var.
    leoTkinterTree.callbacksInjected = True
    self.injectCallbacks()
#@nonl
#@-node:ekr.20040803072955.19:<< inject callbacks into the position class >>
#@-node:ekr.20040803072955.16:__init__
#@+node:ekr.20040803072955.20:createPermanentBindings
def createPermanentBindings (self):
    
    canvas = self.canvas

    if self.expanded_click_area:
        canvas.tag_bind('clickBox','<Button-1>', self.onClickBoxClick)
    else:
        canvas.tag_bind('plusBox','<Button-1>',   self.onClickBoxClick)

    canvas.tag_bind('iconBox','<Button-1>', self.onIconBoxClick)
    canvas.tag_bind('iconBox','<Double-1>', self.onIconBoxDoubleClick)
    canvas.tag_bind('iconBox','<Button-3>', self.onIconBoxRightClick)
    canvas.tag_bind('iconBox','<B1-Motion>',            self.onDrag)
    canvas.tag_bind('iconBox','<Any-ButtonRelease-1>',  self.onEndDrag)

    if self.useBindtags: # Create a dummy widget to hold all bindings.
        t = Tk.Text(canvas) # This _must_ be a Text widget attached to the canvas!
        if 1: # Either way works properly.
            t.bind("<Button-1>", self.onHeadlineClick)
            t.bind("<Button-3>", self.onHeadlineRightClick)
            t.bind("<Key>",      self.onHeadlineKey)
        else:
            t.bind("<Button-1>", self.onHeadlineClick, '+')
            t.bind("<Button-3>", self.onHeadlineRightClick, '+')
            t.bind("<Key>",      self.onHeadlineKey, '+')
        t.bind("<Control-t>",self.onControlT)
    
        # newText() attaches these bindings to all headlines.
        self.textBindings = t.bindtags()
#@nonl
#@-node:ekr.20040803072955.20:createPermanentBindings
#@+node:ekr.20040803072955.7:newBox
def newBox (self,p,x,y,image):
    
    canvas = self.canvas ; tag = "plusBox" # 9/5/04: was plugBox.

    if self.freeBoxes:
        id = self.freeBoxes.pop(0)
        canvas.coords(id,x,y)
        canvas.itemconfigure(id,image=image)
    else:
        id = canvas.create_image(x,y,image=image,tag=tag)
        
    if self.trace and self.verbose:
        g.trace("%3d %3d %3d %8s" % (id,x,y,' '),p.headString(),align=-20)

    assert(id not in self.visibleBoxes)
    self.visibleBoxes.append(id)

    assert(not self.ids.get(id))
    assert(p)
    self.ids[id] = p

    return id
#@nonl
#@-node:ekr.20040803072955.7:newBox
#@+node:ekr.20040803072955.37:drawClickBox
def drawClickBox (self,p,y):

    canvas = self.canvas ; h = self.line_height
    
    # Define a slighly larger rect to catch clicks.
    if self.expanded_click_area:
        id = self.newClickBox(p,0,y,1000,y+h-2)
        
        if 0: # A major change to the user interface.
            << change the appearance of headlines >>
#@nonl
#@+node:ekr.20040803072955.38:<< change the appearance of headlines >>

# Define a slighly smaller rect to colorize.
color_rect = self.canvas.create_rectangle(0,y,1000,y+h-4,tag="colorBox")
self.canvas.itemconfig(color_rect,fill=defaultColor,outline=defaultColor)

# Color the click box or the headline
def enterRect(event,id=color_rect,p=p,t=self.lastText):
    if 1: # Color or underline the headline
        t2 = self.lastColoredText
        if t2: # decolor the old headline.
            if 1: # deunderline
                t2.tag_delete('underline')
            else: # decolor
                t2.configure(background="white")
        if t and p != self.editPosition():
            if 1: # underline
                t.tag_add('underline','1.0','end')
                t.tag_configure('underline',underline=True)
            else: # color
                t.configure(background="LightSteelBlue1")
            self.lastColoredText = t
        else: self.lastColoredText = None
    else: # Color the click box.
        if self.lastClickFrameId:
            self.canvas.itemconfig(self.lastClickFrameId,fill=defaultColor,outline=defaultColor)
        self.lastClickFrameId = id
        color = "LightSteelBlue1"
        self.canvas.itemconfig(id,fill=color,outline=color)

bind_id = self.canvas.tag_bind(click_rect, "<Enter>", enterRect) # , '+')
self.tagBindings.append((click_rect,bind_id,"<Enter>"),)
#@nonl
#@-node:ekr.20040803072955.38:<< change the appearance of headlines >>
#@-node:ekr.20040803072955.37:drawClickBox
#@-node:ekr.20040901061935:(Added option to disable left click logic in outline pane)
#@+node:ekr.20040812033107:(Fix bugs in perfect import)  *** Disabled perfect import
@nocolor

One problem is that continued lines don't have to be indented!  This should be fixed asap.

@color
#@nonl
#@+node:ekr.20040828161347:Report
@killcolor

https://sourceforge.net/forum/message.php?msg_id=2711908
By: nobody

import worked ok on a few files already ok since b3,
this one doesn't import well.
<http://users.wpi.edu/~squirrel/programs/piemenu.html>
<http://users.wpi.edu/~squirrel/programs/piemenu.py>

the methods are all in each class declaration node.
the nodes for each method are made and marked but empty.
same with the other 2 classes in the file.
the generated @file had indentation errors...

import to @file doesn't print status to log?
and why must it mark each method node?
did it know there were problems? why no errors?
maybe a config enabled backup dir to put bak files
if there are going to be some incomplete imports.

looks like it got confused on class variables
and some var = funct asssignments after methods.
some got put after @others at the wrong indent.

has anyone tried to import decorators?
no time like the present...
they import ok for a simple @memoize example.

Leo 4.2 beta 3, build  1.152 , August 10, 2004
Python 2.3.3, Tk 8.4.3, win32
e
#@nonl
#@-node:ekr.20040828161347:Report
#@+node:ekr.20040907083404:Notes
@killcolor

A file containing tabs generates diffs for every line, which means that perfect import is totally confused.

I suppose that if perfect import is in effect then maybe we should leave whitespace alone, regardless of tab setting...

Hmmm...
#@nonl
#@-node:ekr.20040907083404:Notes
#@+node:EKR.20040504150046:class mulderUpdateAlgorithm (leoGlobals)
import difflib,shutil

class mulderUpdateAlgorithm:
    
    """A class to update derived files using
    diffs in files without sentinels.
    """
    
    @others
    
def doMulderUpdateAlgorithm(sourcefilename,targetfilename):

    mu = mulderUpdateAlgorithm()

    mu.pull_source(sourcefilename,targetfilename)
    mu.copy_time(targetfilename,sourcefilename)
#@nonl
#@+node:EKR.20040504150046.3:__init__
def __init__ (self,testing=False,verbose=False):
    
    self.testing = testing
    self.verbose = False
    self.do_backups = False
#@nonl
#@-node:EKR.20040504150046.3:__init__
#@+node:EKR.20040504150046.9:copy_sentinels
@ This script retains _all_ sentinels.  If lines are replaced, or deleted,
we restore deleted sentinel lines by checking for gaps in the mapping.
@c

def copy_sentinels (self,write_lines,fat_lines,fat_pos,mapping,startline,endline):
    """
    
    Copy sentinel lines from fat_lines to write_lines.

    Copy all sentinels _after_ the current reader postion up to,
    but not including, mapping[endline].

    """

    j_last = mapping[startline]
    i = startline + 1
    while i <= endline:
        j = mapping[i]
        if j_last + 1 != j:
            fat_pos = j_last + 1
            # Copy the deleted sentinels that comprise the gap.
            while fat_pos < j:
                line = fat_lines[fat_pos]
                write_lines.append(line)
                if self.testing and self.verbose: print "Copy sentinel:",fat_pos,line,
                fat_pos += 1
        j_last = j ; i += 1

    fat_pos = mapping[endline]
    return fat_pos
#@nonl
#@-node:EKR.20040504150046.9:copy_sentinels
#@+node:EKR.20040504155109:copy_time
def copy_time(self,sourcefilename,targetfilename):
    
    """
    Set the target file's modification time to
    that of the source file.
    """

    st = os.stat(sourcefilename)

    if hasattr(os, 'utime'):
        os.utime(targetfilename, (st.st_atime, st.st_mtime))
    elif hasattr(os, 'mtime'):
        os.mtime(targetfilename, st.st_mtime)
    else:
        g.trace("Can not set modification time")
#@nonl
#@-node:EKR.20040504155109:copy_time
#@+node:EKR.20040504150046.6:create_mapping
def create_mapping (self,lines,delims):
    """

    'lines' is a list of lines of a file with sentinels.
 
    Returns:

    result: lines with all sentinels removed.

    mapping: a list such that result[mapping[i]] == lines[i]
    for all i in range(len(result))

    """
    
    if not lines:
        return [],[]

    # Create mapping and set i to the index of the last non-sentinel line.
    mapping = []
    for i in xrange(len(lines)):
        if not g.is_sentinel(lines[i],delims):
            mapping.append(i)

    # Create a last mapping entry for copy_sentinels.
    mapping.append(i)
    
    # Use removeSentinelsFromLines to handle @nonl properly.
    stripped_lines = self.removeSentinelsFromLines(lines,delims)

    return stripped_lines, mapping
#@nonl
#@-node:EKR.20040504150046.6:create_mapping
#@+node:EKR.20040505080156:Get or remove sentinel lines
# These routines originally were part of push_filter & push_filter_lines.
#@nonl
#@+node:EKR.20040505081121:separateSentinelsFromFile/Lines
def separateSentinelsFromFile (self,filename):
    
    """Separate the lines of the file into a tuple of two lists,
    containing the sentinel and non-sentinel lines of the file."""
    
    lines = file(filename).readlines()
    delims = g.comment_delims_from_extension(filename)
    
    return self.separateSentinelsFromLines(lines,delims)
    
def separateSentinelsFromLines (self,lines,delims):
    
    """Separate lines (a list of lines) into a tuple of two lists,
    containing the sentinel and non-sentinel lines of the original list."""
    
    strippedLines = self.removeSentinelsFromLines(lines,delims)
    sentinelLines = self.getSentinelsFromLines(lines,delims)
    
    return strippedLines,sentinelLines
#@nonl
#@-node:EKR.20040505081121:separateSentinelsFromFile/Lines
#@+node:EKR.20040505080156.2:removeSentinelsFromFile/Lines
def removeSentinelsFromFile (self,filename):
    
    """Return a copy of file with all sentinels removed."""
    
    lines = file(filename).readlines()
    delims = g.comment_delims_from_extension(filename)
    
    return removeSentinelsFromLines(lines,delims)
    
def removeSentinelsFromLines (self,lines,delims):

    """Return a copy of lines with all sentinels removed."""
    
    delim1,delim2,delim3 = delims
    result = [] ; last_nosent_i = -1
    for i in xrange(len(lines)):
        if not g.is_sentinel(lines[i],delims):
            result.append(lines[i])
            last_nosent_i = i
    << remove the newline from result[-1] if line[i] is followed by @nonl >>
    return result
#@nonl
#@+node:ekr.20040716105102:<< remove the newline from result[-1] if line[i] is followed by @nonl >>
i = last_nosent_i

if i + 1 < len(lines):

    line = lines[i+1]
    j = g.skip_ws(line,0)

    if match(line,j,delim1):
        j += len(delim1)

        if g.match(line,j,"@nonl"):
            line = lines[i]
            if line[-1] == '\n':
                assert(result[-1] == line)
                result[-1] = line[:-1]
#@nonl
#@-node:ekr.20040716105102:<< remove the newline from result[-1] if line[i] is followed by @nonl >>
#@-node:EKR.20040505080156.2:removeSentinelsFromFile/Lines
#@+node:EKR.20040505080156.3:getSentinelsFromFile/Lines
def getSentinelsFromFile (self,filename,delims):
    
    """Returns all sentinels lines in a file."""
    
    lines = file(filename).readlines()
    delims = g.comment_delims_from_extension(filename)

    return getSentinelsFromLines(lines,delims)
    
def getSentinelsFromLines (self,lines,delims):
    
    """Returns all sentinels lines in lines."""
    
    return [line for line in lines if g.is_sentinel(line,delims)]
#@nonl
#@-node:EKR.20040505080156.3:getSentinelsFromFile/Lines
#@-node:EKR.20040505080156:Get or remove sentinel lines
#@+node:EKR.20040504150046.10:propagateDiffsToSentinelsFile
def propagateDiffsToSentinelsFile(self,sourcefilename,targetfilename):
    
    << init propagateDiffsToSentinelsFile vars >>
    
    write_lines = self.propagateDiffsToSentinelsLines(
        i_lines,j_lines,fat_lines,mapping)
        
    # Update _source_ file if it is not the same as write_lines.
    written = self.write_if_changed(write_lines,targetfilename,sourcefilename)
    if written:
        << paranoia check>>
#@nonl
#@+node:EKR.20040504150046.11:<< init propagateDiffsToSentinelsFile vars >>
# Get the sentinel comment delims.
delims = self.comment_delims_from_extension(sourcefilename)
if not delims:
    return

try:
    # Create the readers.
    sfile = file(sourcefilename)
    tfile = file(targetfilename)
    
    fat_lines = sfile.readlines() # Contains sentinels.
    j_lines   = tfile.readlines() # No sentinels.
    
    i_lines,mapping = self.create_mapping(fat_lines,delims)
    
    sfile.close()
    tfile.close()
except:
    g.es_exception("can not open files")
    return
#@nonl
#@-node:EKR.20040504150046.11:<< init propagateDiffsToSentinelsFile vars >>
#@+node:EKR.20040504150046.12:<<paranoia check>>
# Check that 'push' will re-create the changed file.
strippedLines,sentinel_lines = self.separateSentinelsFromFile(sourcefilename)

if strippedLines != j_lines:
    self.report_mismatch(strippedLines, j_lines,
        "Propagating diffs did not work as expected",
        "Content of sourcefile:",
        "Content of modified file:")

# Check that no sentinels got lost.
fat_sentinel_lines = self.getSentinelsFromLines(fat_lines,delims)

if sentinel_lines != fat_sentinel_lines:
    self.report_mismatch(sentinel_lines,fat_sentinel_lines,
        "Propagating diffs modified sentinel lines:",
        "Current sentinel lines:",
        "Old sentinel lines:")
#@nonl
#@-node:EKR.20040504150046.12:<<paranoia check>>
#@-node:EKR.20040504150046.10:propagateDiffsToSentinelsFile
#@+node:EKR.20040504145804.1:propagateDiffsToSentinelsLines (called from perfect import)
def propagateDiffsToSentinelsLines (self,
    i_lines,j_lines,fat_lines,mapping):
    
    """Compare the 'i_lines' with 'j_lines' and propagate the diffs back into
    'write_lines' making sure that all sentinels of 'fat_lines' are copied.

    i/j_lines have no sentinels.  fat_lines does."""

    << init propagateDiffsToSentinelsLines vars >>
    << copy the sentinels at the beginning of the file >>
    for tag, i1, i2, j1, j2 in matcher.get_opcodes():
        if testing:
            if verbose: print
            print "Opcode %7s %3d %3d %3d %3d" % (tag,i1,i2,j1,j2)
            if verbose: print
        << update and check the loop invariant >>
        if tag == 'equal':
            << handle 'equal' tag >>
        elif tag == 'replace':
            << handle 'replace' tag >>
        elif tag == 'delete':
            << handle 'delete' tag >>
        elif tag == 'insert':
            << handle 'insert' tag >>
        else: assert 0,"bad tag"
    << copy the sentinels at the end of the file >>
    return write_lines
#@nonl
#@+node:EKR.20040504145804.2:<< init propagateDiffsToSentinelsLines vars >>
# Indices into i_lines, j_lines & fat_lines.
i_pos = j_pos = fat_pos = 0

# These vars check that all ranges returned by get_opcodes() are contiguous.
i2_old = j2_old = -1

# Create the output lines.
write_lines = []

matcher = difflib.SequenceMatcher(None,i_lines,j_lines)

testing = self.testing
verbose = self.verbose
#@nonl
#@-node:EKR.20040504145804.2:<< init propagateDiffsToSentinelsLines vars >>
#@+node:EKR.20040504145804.3:<< copy the sentinels at the beginning of the file >>
while fat_pos < mapping[0]:

    line = fat_lines[fat_pos]
    write_lines.append(line)
    if testing:
        print "copy initial line",fat_pos,line,
    fat_pos += 1
#@-node:EKR.20040504145804.3:<< copy the sentinels at the beginning of the file >>
#@+node:EKR.20040504145804.4:<< update and check the loop invariant>>
# We need the ranges returned by get_opcodes to completely cover the source lines being compared.
# We also need the ranges not to overlap.

assert(i2_old in (-1,i1))
assert(j2_old in (-1,j1))

i2_old = i2 ; j2_old = j2

# Check the loop invariants.
assert i_pos == i1
assert j_pos == j1
assert fat_pos == mapping[i1]

if 0: # not yet.
    if testing: # A bit costly.
        t_sourcelines,t_sentinel_lines = push_filter_lines(write_lines, delims)
        # Check that we have all the modifications so far.
        assert t_sourcelines == j_lines[:j1],"t_sourcelines == j_lines[:j1]"
        # Check that we kept all sentinels so far.
        assert t_sentinel_lines == push_filter_lines(fat_lines[:fat_pos], delims)[1]
#@nonl
#@-node:EKR.20040504145804.4:<< update and check the loop invariant>>
#@+node:EKR.20040504145804.5:<< handle 'equal' tag >>
# Copy the lines, including sentinels.
while fat_pos <= mapping[i2-1]:
    line = fat_lines[fat_pos]
    if 0: # too verbose.
        if testing: print "Equal: copying ", line,
    write_lines.append(line)
    fat_pos += 1

if testing and verbose:
    print "Equal: synch i", i_pos,i2
    print "Equal: synch j", j_pos,j2

i_pos = i2
j_pos = j2

# Copy the sentinels which might follow the lines.       
fat_pos = self.copy_sentinels(write_lines,fat_lines,fat_pos,mapping,i2-1,i2)
#@nonl
#@-node:EKR.20040504145804.5:<< handle 'equal' tag >>
#@+node:EKR.20040504145804.6:<< handle 'replace' tag >>
@ Replace lines that may span sentinels.

For now, we put all the new contents after the first sentinel.

A more complex approach: run the difflib across the different lines and try to
construct a mapping changed line => orignal line.
@c

while j_pos < j2:
    line = j_lines[j_pos]
    if testing:
        print "Replace i:",i_pos,repr(i_lines[i_pos])
        print "Replace j:",j_pos,repr(line)
        i_pos += 1

    write_lines.append(line)
    j_pos += 1

i_pos = i2

# Copy the sentinels which might be between the changed code.         
fat_pos = self.copy_sentinels(write_lines,fat_lines,fat_pos,mapping,i1,i2)
#@nonl
#@-node:EKR.20040504145804.6:<< handle 'replace' tag >>
#@+node:EKR.20040504145804.7:<< handle 'delete' tag >>
if testing and verbose:
    print "delete: i",i_pos,i1
    print "delete: j",j_pos,j1

j_pos = j2
i_pos = i2

# Restore any deleted sentinels.
fat_pos = self.copy_sentinels(write_lines,fat_lines,fat_pos,mapping,i1,i2)
#@nonl
#@-node:EKR.20040504145804.7:<< handle 'delete' tag >>
#@+node:EKR.20040504145804.8:<< handle 'insert' tag >>
while j_pos < j2:
    line = j_lines[j_pos]
    if testing: print "Insert:", line,
    write_lines.append(line)
    j_pos += 1

# The input streams are already in synch.
#@nonl
#@-node:EKR.20040504145804.8:<< handle 'insert' tag >>
#@+node:EKR.20040504145804.9:<< copy the sentinels at the end of the file >>
while fat_pos < len(fat_lines):

    line = fat_lines[fat_pos]
    write_lines.append(line)
    if testing:
        print "Append last line",line
    fat_pos += 1
#@-node:EKR.20040504145804.9:<< copy the sentinels at the end of the file >>
#@-node:EKR.20040504145804.1:propagateDiffsToSentinelsLines (called from perfect import)
#@+node:EKR.20040504150046.5:report_mismatch
def report_mismatch (self,lines1,lines2,message,lines1_message,lines2_message):

    """
    Generate a report when something goes wrong.
    """

    print '='*20
    print message
    
    if 0:
        print lines1_message
        print '-'*20
        for line in lines1:
          print line,
         
        print '='*20
    
        print lines2_message
        print '-'*20
        for line in lines2:
            print line,
#@nonl
#@-node:EKR.20040504150046.5:report_mismatch
#@+node:ekr.20040718101315:stripWhitespaceFromBlankLines(before_lines)
def stripWhitespaceFromBlankLines (self,lines):
    
    # All backslashes must be doubled.

    """Strip blanks and tabs from lines containing only blanks and tabs.
    
    >>> import leoGlobals as g
    >>> s = "a\\n \\t\\n\\t\\t \\t\\nb"
    >>> theLines = g.splitLines(s)
    >>> theLines
    ['a\\n', ' \\t\\n', '\\t\\t \\t\\n', 'b']
    >>> g.mulderUpdateAlgorithm().stripWhitespaceFromBlankLines(theLines)
    ['a\\n', '\\n', '\\n', 'b']
    """

    for i in xrange(len(lines)):
        stripped_line = lines[i].lstrip(" \t")
        if stripped_line in ('\n',''):
            lines[i] = stripped_line
            
    return lines
#@nonl
#@-node:ekr.20040718101315:stripWhitespaceFromBlankLines(before_lines)
#@+node:EKR.20040504160820:write_if_changed
def write_if_changed(self,lines,sourcefilename,targetfilename):
    """
    
    Replaces target file if it is not the same as 'lines',
    and makes the modification date of target file the same as the source file.
    
    Optionally backs up the overwritten file.

    """
    
    copy = not os.path.exists(targetfilename) or lines != file(targetfilename).readlines()
        
    if self.testing:
        if copy:
            print "Writing",targetfilename,"without sentinals"
        else:
            print "Files are identical"

    if copy:
        if self.do_backups:
            << make backup file >>
        outfile = open(targetfilename, "w")
        for line in lines:
            outfile.write(line)
        outfile.close()
        self.copy_time(sourcefilename,targetfilename)
    return copy
#@+node:EKR.20040504160820.1:<< make backup file >>
if os.path.exists(targetfilename):
    count = 0
    backupname = "%s.~%s~" % (targetfilename,count)
    while os.path.exists(backupname):
        count += 1
        backupname = "%s.~%s~" % (targetfilename,count)
    os.rename(targetfilename, backupname)
    if testing:
        print "backup file in ", backupname
#@nonl
#@-node:EKR.20040504160820.1:<< make backup file >>
#@-node:EKR.20040504160820:write_if_changed
#@-node:EKR.20040504150046:class mulderUpdateAlgorithm (leoGlobals)
#@+node:EKR.20040506075328.2:perfectImport
def perfectImport (self,fileName,p,testing=False,verbose=False,convertBlankLines=True,verify=True):
    
    << about this algorithm >>
    c = p.c ; root = p.copy()
    df = c.atFileCommands.new_df
    if testing:
        << clear all dirty bits >>
    << Assign file indices >>
    << Write root's tree to to string s >>

    # Set up the data for the algorithm.
    mu = g.mulderUpdateAlgorithm(testing=testing,verbose=verbose)
    delims = g.comment_delims_from_extension(fileName)
    fat_lines = g.splitLines(s) # Keep the line endings.
    i_lines,mapping = mu.create_mapping(fat_lines,delims)
    j_lines = file(fileName).readlines()
    
    # Correct write_lines using the algorihm.
    if i_lines != j_lines:
        if verbose:
            g.es("Running Perfect Import",color="blue")
        write_lines = mu.propagateDiffsToSentinelsLines(i_lines,j_lines,fat_lines,mapping)
        if 1: # For testing.
            << put the corrected fat lines in a new node >>
        << correct root's tree using write_lines >>
    if verify:
        << verify that writing the tree would produce the original file >>
#@nonl
#@+node:ekr.20040717112739:<< about this algorithm >>
@nocolor
@

This algorithm corrects the result of an Import To @file command so that it is guaranteed that the result of writing the imported file will be identical to the original file except for any sentinels that have been inserted.

On entry, p points to the newly imported outline.

We correct the outline by applying Bernhard Mulder's algorithm.

1.  We use the atFile.write code to write the newly imported outline to a string s.  This string contains represents a thin derived file, so it can be used to recreate then entire outline structure without any other information.

Splitting s into lines creates the fat_lines argument to mu methods.

2. We make corrections to fat_lines using Mulder's algorithm.  The corrected fat_lines represents the corrected outline.  To do this, we set the arguments as follows:

- i_lines: fat_lines stripped of sentinels
- j_lines to the lines of the original imported file.

The algorithm updates fat_lines using diffs between i_lines and j_lines.

3. Mulder's algorithm doesn't specify which nodes have been changed.  In fact, it Mulder's algorithm doesn't really understand nodes at all.  Therefore, if we want to mark changed nodes we do so by comparing the original version of the imported outline with the corrected version of the outline.
#@nonl
#@-node:ekr.20040717112739:<< about this algorithm >>
#@+node:ekr.20040716065356:<< clear all dirty bits >>
for p2 in p.self_and_subtree_iter():
    p2.clearDirty()
#@nonl
#@-node:ekr.20040716065356:<< clear all dirty bits >>
#@+node:ekr.20040716064333:<< Assign file indices  >>
nodeIndices = g.app.nodeIndices

nodeIndices.setTimestamp()

for p2 in root.self_and_subtree_iter():
    try: # Will fail for None or any pre 4.1 file index.
        id,time,n = p2.v.t.fileIndex
    except TypeError:
        p2.v.t.fileIndex = nodeIndices.getNewIndex()
#@nonl
#@-node:ekr.20040716064333:<< Assign file indices  >>
#@+node:ekr.20040716064333.1:<< Write root's tree to to string s >>
df.write(root,thinFile=True,toString=True)
s = df.stringOutput
if not s: return
#@-node:ekr.20040716064333.1:<< Write root's tree to to string s >>
#@+node:ekr.20040717132539:<< put the corrected fat lines in a new node >>
write_lines_node = root.insertAfter()
write_lines_node.initHeadString("write_lines")
s = ''.join(write_lines)
write_lines_node.scriptSetBodyString(s,encoding=g.app.tkEncoding)
#@nonl
#@-node:ekr.20040717132539:<< put the corrected fat lines in a new node >>
#@+node:ekr.20040717113036:<< correct root's tree using write_lines >>
@ Notes:
1. This code must overwrite the newly-imported tree because the gnx's in
write_lines refer to those nodes.

2. The code in readEndNode now reports when nodes change during importing. This
code also marks changed nodes.
@c

try:
    df.correctedLines = 0
    df.targetFileName = "<perfectImport string-file>"
    df.inputFile = fo = g.fileLikeObject()
    df.file = fo # Strange, that this is needed.  Should be cleaned up.
    for line in write_lines:
        fo.write(line)
    firstLines,junk = c.atFileCommands.scanHeader(fo,df.targetFileName)
    # To do: pass params to readEndNode.
    df.readOpenFile(root,fo,firstLines,perfectImportRoot=root)
    n = df.correctedLines
    if verbose:
        g.es("%d marked node%s corrected" % (n,g.choose(n==1,'','s')),color="blue")
except:
    g.es("Exception in Perfect Import",color="red")
    g.es_exception()
    s = None
#@nonl
#@-node:ekr.20040717113036:<< correct root's tree using write_lines >>
#@+node:ekr.20040718035658:<< verify that writing the tree would produce the original file >>
try:
    # Read the original file into before_lines.
    before = file(fileName)
    before_lines = before.readlines()
    before.close()
    
    # Write the tree into after_lines.
    df.write(root,thinFile=True,toString=True)
    after_lines1 = g.splitLines(df.stringOutput)
    
    # Strip sentinels from after_lines and compare.
    after_lines = mu.removeSentinelsFromLines(after_lines1,delims)
    
    # A major kludge: Leo can not represent unindented blank lines in indented nodes!
    # We ignore the problem here by stripping whitespace from blank lines.
    # We shall need output options to handle such lines.
    if convertBlankLines:
        mu.stripWhitespaceFromBlankLines(before_lines)
        mu.stripWhitespaceFromBlankLines(after_lines)
    if before_lines == after_lines:
        if verbose:
            g.es("Perfect Import verified",color="blue")
    else:
        leoTest.fail()
        if verbose:
            g.es("Perfect Import failed verification test!",color="red")
            << dump the files >>
except IOError:
    g.es("Can not reopen %s!" % fileName,color="red")
    leoTest.fail()
#@nonl
#@+node:ekr.20040718045423:<< dump the files >>
print len(before_lines),len(after_lines)

if len(before_lines)==len(after_lines):
    for i in xrange(len(before_lines)):
        extra = 3
        if before_lines[i] != after_lines[i]:
            j = max(0,i-extra)
            print '-' * 20
            while j < i + extra + 1:
                leader = g.choose(i == j,"* ","  ")
                print "%s%3d" % (leader,j), repr(before_lines[j])
                print "%s%3d" % (leader,j), repr(after_lines[j])
                j += 1
else:
    for i in xrange(min(len(before_lines),len(after_lines))):
        if before_lines[i] != after_lines[i]:
            extra = 5
            print "first mismatch at line %d" % i
            print "printing %d lines after mismatch" % extra
            print "before..."
            for j in xrange(i+1+extra):
                print "%3d" % j, repr(before_lines[j])
            print
            print "after..."
            for k in xrange(1+extra):
                print "%3d" % (i+k), repr(after_lines[i+k])
            print
            print "with sentinels"
            j = 0 ; k = 0
            while k < i + 1 + extra:
                print "%3d" % k,repr(after_lines1[j])
                if not g.is_sentinel(after_lines1[j],delims):
                    k += 1
                j += 1
            break
#@nonl
#@-node:ekr.20040718045423:<< dump the files >>
#@-node:ekr.20040718035658:<< verify that writing the tree would produce the original file >>
#@-node:EKR.20040506075328.2:perfectImport
#@+node:ekr.20031218072017.3212:importFilesCommand
def importFilesCommand (self,files,treeType,
    perfectImport=True,testing=False,verbose=False):

    c = self.c
    if c == None: return
    v = current = c.currentVnode()
    if current == None: return
    if len(files) < 1: return
    self.treeType = treeType
    c.beginUpdate()
    if 1: # range of update...
        if len(files) == 2:
            << Create a parent for two files having a common prefix >>
        for fileName in files:
            v = self.createOutline(fileName,current)
            if v: # createOutline may fail.
                perfectImport = False ###
                testing = True; verbose = True
                if perfectImport and treeType == "@file": # Can't correct @root trees.
                    self.perfectImport(fileName,v,testing=testing,verbose=verbose,verify=False)
                else:
                    g.es("imported " + fileName,color="blue")
                v.contract()
                v.setDirty()
                c.setChanged(True)
        c.validateOutline()
        current.expand()
    c.endUpdate()
    c.selectVnode(current)
#@nonl
#@+node:ekr.20031218072017.3213:<< Create a parent for two files having a common prefix >>
@ The two filenames have a common prefix everything before the last period is the same.  For example, x.h and x.cpp.
@c

name0 = files[0]
name1 = files[1]
prefix0, junk = g.os_path_splitext(name0)
prefix1, junk = g.os_path_splitext(name1)
if len(prefix0) > 0 and prefix0 == prefix1:
    current = current.insertAsLastChild()
    junk, nameExt = g.os_path_split(prefix1)
    name,ext = g.os_path_splitext(prefix1)
    current.initHeadString(name)
#@nonl
#@-node:ekr.20031218072017.3213:<< Create a parent for two files having a common prefix >>
#@-node:ekr.20031218072017.3212:importFilesCommand
#@+node:ekr.20031218072017.3319:undentBody
# We look at the first line to determine how much leading whitespace to delete.

def undentBody (self,s):

    """Removes extra leading indentation from all lines."""

    # g.trace(s)
    c = self.c
    i = 0 ; result = ""
    # Copy an @code line as is.
    if g.match(s,i,"@code"):
        j = i ; i = g.skip_line(s,i) # don't use get_line: it is only for dumping.
        result += s[j:i]
    # Calculate the amount to be removed from each line.
    undent = self.getLeadingIndent(s,i)
    if undent == 0: return s
    while i < len(s):
        j = i ; i = g.skip_line(s,i) # don't use get_line: it is only for dumping.
        line = s[j:i]
        # g.trace(line)
        line = g.removeLeadingWhitespace(line,undent,c.tab_width)
        result += line
    return result
#@nonl
#@-node:ekr.20031218072017.3319:undentBody
#@+node:ekr.20031218072017.3202:removeLeadingWhitespace
# Remove whitespace up to first_ws wide in s, given tab_width, the width of a tab.

def removeLeadingWhitespace (s,first_ws,tab_width):

    j = 0 ; ws = 0
    for ch in s:
        if ws >= first_ws:
            break
        elif ch == ' ':
            j += 1 ; ws += 1
        elif ch == '\t':
            j += 1 ; ws += (abs(tab_width) - (ws % abs(tab_width)))
        else: break
    if j > 0:
        s = s[j:]
    return s
#@nonl
#@-node:ekr.20031218072017.3202:removeLeadingWhitespace
#@+node:ekr.20031218072017.2256:Python scanners
#@+node:ekr.20031218072017.2257:scanPythonClass
def scanPythonClass (self,s,i,start,parent):

    """Creates a child node c of parent for the class, and children of c for each def in the class."""

    # g.trace(g.get_line(s,i))
    classIndent = self.getLeadingIndent(s,i)
    << set classname and headline, or return i >>
    i = g.skip_line(s,i) # Skip the class line.
    << create class_vnode >>
    savedMethodName = self.methodName
    self.methodName = headline
    # Create a node for leading declarations of the class.
    i = self.scanPythonDecls(s,i,class_vnode,classIndent,indent_parent_ref_flag=True)
    << create nodes for all defs of the class >>
    << append any other class material >>
    self.methodName = savedMethodName
    return i
#@+node:ekr.20031218072017.2258:<< set classname and headline, or return i >>
# Skip to the class name.
i = g.skip_ws(s,i)
i = g.skip_c_id(s,i) # skip "class"
i = g.skip_ws_and_nl(s,i)
if i < len(s) and g.is_c_id(s[i]):
    j = i ; i = g.skip_c_id(s,i)
    classname = s[j:i]
    headline = "class " + classname
else:
    return i
#@nonl
#@-node:ekr.20031218072017.2258:<< set classname and headline, or return i >>
#@+node:ekr.20031218072017.2259:<< create class_vnode  >>
# Create the section name using the old value of self.methodName.
if  self.treeType == "@file":
    prefix = ""
else:
    prefix = g.angleBrackets(" " + self.methodName + " methods ") + "=\n\n"
    self.methodsSeen = True

# i points just after the class line.

# Add a docstring to the class node.
docStringSeen = False
j = g.skip_ws_and_nl(s,i)
if g.match(s,j,'"""') or g.match(s,j,"'''"):
    j = g.skip_python_string(s,j)
    if j != len(s): # No scanning error.
        i = j ; docStringSeen = True

body = s[start:i]
body = self.undentBody(body)
if docStringSeen: body = body + '\n'
class_vnode = self.createHeadline(parent,prefix + body,headline)
#@nonl
#@-node:ekr.20031218072017.2259:<< create class_vnode  >>
#@+node:ekr.20031218072017.2260:<< create nodes for all defs of the class >>
indent =  self.getLeadingIndent(s,i)
start = i = g.skip_blank_lines(s,i)
parent_vnode = None # 7/6/02
while i < len(s) and indent > classIndent:
    progress = i
    if g.is_nl(s,i):
        backslashNewline = i > 0 and g.match(s,i-1,"\\\n")
        j = g.skip_nl(s,i)
        if not backslashNewline:
            indent = self.getLeadingIndent(s,j)
            if indent > classIndent: i = j
            else: break
        else: i = j
    elif g.match_c_word(s,i,"def"):
        if not parent_vnode:
            << create parent_vnode >>
        i = start = self.scanPythonDef(s,i,start,parent_vnode)
        indent = self.getLeadingIndent(s,i)
    elif g.match_c_word(s,i,"class"):
        if not parent_vnode:
            << create parent_vnode >>
        i = start = self.scanPythonClass(s,i,start,parent_vnode)
        indent = self.getLeadingIndent(s,i)
    elif s[i] == '#': i = g.skip_to_end_of_line(s,i)
    elif s[i] == '"' or s[i] == '\'': i = g.skip_python_string(s,i)
    else: i += 1
    assert(progress < i)
#@nonl
#@+node:ekr.20031218072017.2261:<< create parent_vnode >>
# This must be done after the declaration reference is generated.
if self.treeType == "@file":
    class_vnode.appendStringToBody("\t@others\n")
else:
    ref = g.angleBrackets(" class " + classname + " methods ")
    class_vnode.appendStringToBody("\t" + ref + "\n\n")
parent_vnode = class_vnode
#@nonl
#@-node:ekr.20031218072017.2261:<< create parent_vnode >>
#@-node:ekr.20031218072017.2260:<< create nodes for all defs of the class >>
#@+node:ekr.20031218072017.2262:<< append any other class material >>
s2 = s[start:i]
if s2:
    class_vnode.appendStringToBody(s2)
#@nonl
#@-node:ekr.20031218072017.2262:<< append any other class material >>
#@-node:ekr.20031218072017.2257:scanPythonClass
#@+node:ekr.20031218072017.2263:scanPythonDef
def scanPythonDef (self,s,i,start,parent):

    """Creates a node of parent for the def."""

    # g.trace(g.get_line(s,i))
    << set headline or return i >>
    << skip the Python def >>
    # Create the def node.
    savedMethodName = self.methodName
    self.methodName = headline
    << Create def node >>
    self.methodName = savedMethodName
    return i
#@+node:ekr.20031218072017.2264:<< set headline or return i >>
i = g.skip_ws(s,i)
i = g.skip_c_id(s,i) # Skip the "def"
i = g.skip_ws_and_nl(s,i)
if i < len(s) and g.is_c_id(s[i]):
    j = i ; i = g.skip_c_id(s,i)
    headline = s[j:i]
    # g.trace("headline:" + headline)
else: return i
#@nonl
#@-node:ekr.20031218072017.2264:<< set headline or return i >>
#@+node:ekr.20031218072017.2265:<< skip the Python def >>
# Set defIndent to the indentation of the def line.
defIndent = self.getLeadingIndent(s,start)
i = g.skip_line(s,i) # Skip the def line.
indent = self.getLeadingIndent(s,i)
while i < len(s) and indent > defIndent:
    progress = i
    ch = s[i]
    if g.is_nl(s,i):
        backslashNewline = i > 0 and g.match(s,i-1,"\\\n")
        i = g.skip_nl(s,i)
        if not backslashNewline:
            indent = self.getLeadingIndent(s,i)
            if indent <= defIndent:
                break
    elif ch == '#':
        i = g.skip_to_end_of_line(s,i) # 7/29/02
    elif ch == '"' or ch == '\'':
        i = g.skip_python_string(s,i)
    else: i += 1
    assert(progress < i)
#@nonl
#@-node:ekr.20031218072017.2265:<< skip the Python def >>
#@+node:ekr.20031218072017.2266:<< Create def node >>
# Create the prefix line for @root trees.
if self.treeType == "@file":
    prefix = ""
else:
    prefix = g.angleBrackets(" " + savedMethodName + " methods ") + "=\n\n"
    self.methodsSeen = True

# Create body.
start = g.skip_blank_lines(s,start)
body = s[start:i]
body = self.undentBody(body)

# Create the node.
self.createHeadline(parent,prefix + body,headline)

#@-node:ekr.20031218072017.2266:<< Create def node >>
#@-node:ekr.20031218072017.2263:scanPythonDef
#@+node:ekr.20031218072017.2267:scanPythonDecls
def scanPythonDecls (self,s,i,parent,indent,indent_parent_ref_flag=True):
    
    done = False ; start = i
    while not done and i < len(s):
        progress = i
        # g.trace(g.get_line(s,i))
        ch = s[i]
        if ch == '\n':
            backslashNewline = i > 0 and g.match(s,i-1,"\\\n")
            i = g.skip_nl(s,i)
            # 2/14/03: break on lesser indention.
            j = g.skip_ws(s,i)
            if not g.is_nl(s,j) and not g.match(s,j,"#") and not backslashNewline:
                lineIndent = self.getLeadingIndent(s,i)
                if lineIndent <= indent:
                    break
        elif ch == '#': i = g.skip_to_end_of_line(s,i)
        elif ch == '"' or ch == '\'':
            i = g.skip_python_string(s,i)
        elif g.is_c_id(ch):
            << break on def or class >>
        else: i += 1
        assert(progress < i)
    j = g.skip_blank_lines(s,start)
    if g.is_nl(s,j): j = g.skip_nl(s,j)
    if j < i:
        << Create a child node for declarations >>
    return i
#@nonl
#@+node:ekr.20031218072017.2268:<< break on def or class >>
if g.match_c_word(s,i,"def") or g.match_c_word(s,i,"class"):
    i = g.find_line_start(s,i)
    done = True
    break
else:
    i = g.skip_c_id(s,i)
#@nonl
#@-node:ekr.20031218072017.2268:<< break on def or class >>
#@+node:ekr.20031218072017.2269:<< Create a child node for declarations >>
headline = ref = g.angleBrackets(" " + self.methodName + " declarations ")
leading_tab = g.choose(indent_parent_ref_flag,"\t","")

# Append the reference to the parent's body.
parent.appendStringToBody(leading_tab + ref + "\n") # 7/6/02

# Create the node for the decls.
body = self.undentBody(s[j:i])
if self.treeType == "@root":
    body = "@code\n\n" + body
self.createHeadline(parent,body,headline)
#@nonl
#@-node:ekr.20031218072017.2269:<< Create a child node for declarations >>
#@-node:ekr.20031218072017.2267:scanPythonDecls
#@+node:ekr.20031218072017.2270:scanPythonText
# See the comments for scanCText for what the text looks like.

def scanPythonText (self,s,parent):

    """Creates a child of parent for each Python function definition seen."""

    decls_seen = False ; start = i = 0
    self.methodsSeen = False
    while i < len(s):
        progress = i
        # g.trace(g.get_line(s,i))
        ch = s[i]
        if ch == '\n' or ch == '\r': i = g.skip_nl(s,i)
        elif ch == '#': i = g.skip_to_end_of_line(s,i)
        elif ch == '"' or ch == '\'': i = g.skip_python_string(s,i)
        elif g.is_c_id(ch):
            << handle possible Python function or class >>
        else: i += 1
        assert(progress < i)
    if not decls_seen: # 2/17/03
        parent.appendStringToBody("@ignore\n" + self.rootLine + "@language python\n")
    << Append a reference to the methods of this file >>
    << Append any unused python text to the parent's body text >>
#@nonl
#@+node:ekr.20031218072017.2271:<< handle possible Python function or class >>
if g.match_c_word(s,i,"def") or g.match_word(s,i,"class"):
    isDef = g.match_c_word(s,i,"def")
    if not decls_seen:
        parent.appendStringToBody("@ignore\n" + self.rootLine + "@language python\n")
        i = start = self.scanPythonDecls(s,start,parent,-1,indent_parent_ref_flag=False)
        decls_seen = True
        if self.treeType == "@file": # 7/29/02
            parent.appendStringToBody("@others\n") # 7/29/02
    if isDef:
        i = start = self.scanPythonDef(s,i,start,parent)
    else:
        i = start = self.scanPythonClass(s,i,start,parent)
else:
    i = g.skip_c_id(s,i)
#@nonl
#@-node:ekr.20031218072017.2271:<< handle possible Python function or class >>
#@+node:ekr.20031218072017.2272:<< Append a reference to the methods of this file >>
if self.treeType == "@root" and self.methodsSeen:
    parent.appendStringToBody(
        g.angleBrackets(" " + self.methodName + " methods ") + "\n\n")
#@nonl
#@-node:ekr.20031218072017.2272:<< Append a reference to the methods of this file >>
#@+node:ekr.20031218072017.2273:<< Append any unused python text to the parent's body text >>
# Do nothing if only whitespace is left.
i = start ; i = g.skip_ws_and_nl(s,i)
if i < len(s):
    parent.appendStringToBody(s[start:])
#@nonl
#@-node:ekr.20031218072017.2273:<< Append any unused python text to the parent's body text >>
#@-node:ekr.20031218072017.2270:scanPythonText
#@-node:ekr.20031218072017.2256:Python scanners
#@-node:ekr.20040812033107:(Fix bugs in perfect import)  *** Disabled perfect import
#@+node:ekr.20040831112005:(Investigated control-v problems on the Mac)
#@+node:ekr.20040831112005.1:Report
@killcolor

Dan Winkler

> I've noticed that Leo often inserts a v when I hit command-v to paste.  
> I'm sure I've got the command key down before I type the v, but Leo is 
> more sensitive to the timing somehow than most apps.

> Now I'm seeing a problem where when I use command-v to paste into a 
> headline it pastes the text twice.  If I use the edit menu and choose 
> paste it works correctly and only pastes once.  Also, if I paste into 
> the body of a node with command-v, that works correctly too.  It's only 
> when I use the command key to paste into a headline that I see the 
> problem.

EKR:  I don't see any of these problems on my Mac.
#@nonl
#@-node:ekr.20040831112005.1:Report
#@-node:ekr.20040831112005:(Investigated control-v problems on the Mac)
#@+node:EKR.20040422132037.8:Mouse / Paste Anomalies (email sent)
@nocolor

By: Bill Drissel - drissel
2004-04-13 19:43  

Xserver (XFree86 under Cygwin) on PC (W2000); controlling Linux Red Hat;
buncha windows up including one Leo window and many xterms and Xemacs.

A: means action, R: means result

A: hilite text in Xemacs window; move mouse symbol to Leo body pane; middle click
R: text shows in body pane; headline is not marked with blue square; text is not colorized; text is not saved to file

A: hilite text in Xemacs window; move mouse symbol to Leo body pane; middle click; locate mouse symbol in text
R: text shows in body pane; headline is not marked with blue square; text is not colorized; text is not saved to file

A: hilite text in Xemacs window; move mouse symbol to Leo body pane; middle click; up arrow to locate Ibeam in text
R: Joy! colorizes, marks headline and saves!

A: hilite headline text; type on KB
R: keystrokes replace hilited text

A: hilite text in Xemacs window; hilite headline; middle click
R: hilited text appended to headline rather than replacing

This last anomaly seems to persist regardless of how the headline is hilited and regardless of pasting method (middle click or Control-V)
#@nonl
#@-node:EKR.20040422132037.8:Mouse / Paste Anomalies (email sent)
#@+node:ekr.20040910064225:(Removed assert from colorizer)
#@+node:ekr.20040910064225.1:Report
Traceback (most recent call last):
  File "C:\prog\leoCVS\leo\src\leoColor.py", line 1179, in colorizeAnyLanguage
    assert(state!="unknown")
    
This assert failed when when undoing a cut operation.
#@nonl
#@-node:ekr.20040910064225.1:Report
#@+node:ekr.20031218072017.1880:colorizeAnyLanguage & allies
def colorizeAnyLanguage (self,p,leading=None,trailing=None):
    
    """Color the body pane either incrementally or non-incrementally"""
    
    # g.trace("incremental",self.incremental,p)
    if self.killFlag:
        self.removeAllTags()
        return
    try:
        << initialize ivars & tags >>
        g.doHook("init-color-markup",colorer=self,v=self.p)
        self.color_pass = 0
        if self.incremental and (
            << all state ivars match >> ):
            << incrementally color the text >>
        else:
            << non-incrementally color the text >>
        if self.redoColoring:
            << completely recolor in two passes >>
        << update state ivars >>
        return "ok" # for testing.
    except:
        << set state ivars to "unknown" >>
        if self.c:
            g.es_exception()
        else:
            import traceback ; traceback.print_exc()
        return "error" # for unit testing.
#@nonl
#@+node:ekr.20031218072017.1602:<< initialize ivars & tags >> colorizeAnyLanguage
# Add any newly-added user keywords.
for d in g.globalDirectiveList:
    name = '@' + d
    if name not in leoKeywords:
        leoKeywords.append(name)

# Copy the arguments.
self.p = p

# Get the body text, converted to unicode.
s = self.body.getAllText() # 10/27/03
self.sel = sel = self.body.getInsertionPoint() # 10/27/03
start,end = self.body.convertIndexToRowColumn(sel) # 10/27/03

# g.trace(self.language)
# g.trace(self.count,self.p)
# g.trace(body.tag_names())

if not self.incremental:
    self.removeAllTags()
    self.removeAllImages()

self.redoColoring = False
self.redoingColoring = False

<< configure tags >>
<< configure language-specific settings >>

self.hyperCount = 0 # Number of hypertext tags
self.count += 1
lines = string.split(s,'\n')
#@nonl
#@+node:ekr.20031218072017.1603:<< configure tags >>
config = g.app.config
assert(config)

for name in default_colors_dict.keys(): # Python 2.1 support.
    option_name,default_color = default_colors_dict[name]
    option_color = config.getColorsPref(option_name)
    color = g.choose(option_color,option_color,default_color)
    # Must use foreground, not fg.
    try:
        self.body.tag_configure(name, foreground=color)
    except: # Recover after a user error.
        self.body.tag_configure(name, foreground=default_color)

underline_undefined = config.getBoolColorsPref("underline_undefined_section_names")
use_hyperlinks      = config.getBoolColorsPref("use_hyperlinks")
self.use_hyperlinks = use_hyperlinks

# underline=var doesn't seem to work.
if 0: # use_hyperlinks: # Use the same coloring, even when hyperlinks are in effect.
    self.body.tag_configure("link",underline=1) # defined
    self.body.tag_configure("name",underline=0) # undefined
else:
    self.body.tag_configure("link",underline=0)
    if underline_undefined:
        self.body.tag_configure("name",underline=1)
    else:
        self.body.tag_configure("name",underline=0)
        
# 8/4/02: we only create tags for whitespace when showing invisibles.
if self.showInvisibles:
    for name,option_name,default_color in (
        ("blank","show_invisibles_space_background_color","Gray90"),
        ("tab",  "show_invisibles_tab_background_color",  "Gray80")):
        option_color = config.getColorsPref(option_name)
        color = g.choose(option_color,option_color,default_color)
        try:
            self.body.tag_configure(name,background=color)
        except: # Recover after a user error.
            self.body.tag_configure(name,background=default_color)
    
# 11/15/02: Colors for latex characters.  Should be user options...

if 1: # Alas, the selection doesn't show if a background color is specified.
    self.body.tag_configure("latexModeBackground",foreground="black")
    self.body.tag_configure("latexModeKeyword",foreground="blue")
    self.body.tag_configure("latexBackground",foreground="black")
    self.body.tag_configure("latexKeyword",foreground="blue")
else: # Looks cool, and good for debugging.
    self.body.tag_configure("latexModeBackground",foreground="black",background="seashell1")
    self.body.tag_configure("latexModeKeyword",foreground="blue",background="seashell1")
    self.body.tag_configure("latexBackground",foreground="black",background="white")
    self.body.tag_configure("latexKeyword",foreground="blue",background="white")
    
# Tags for wiki coloring.
if self.showInvisibles:
    self.body.tag_configure("elide",background="yellow")
else:
    self.body.tag_configure("elide",elide="1")
self.body.tag_configure("bold",font=self.bold_font)
self.body.tag_configure("italic",font=self.italic_font)
self.body.tag_configure("bolditalic",font=self.bolditalic_font)
for name in self.color_tags_list:
    self.body.tag_configure(name,foreground=name)
#@nonl
#@-node:ekr.20031218072017.1603:<< configure tags >>
#@+node:ekr.20031218072017.370:<< configure language-specific settings >> colorizer
# Define has_string, keywords, single_comment_start, block_comment_start, block_comment_end.

if self.language == "cweb": # Use C comments, not cweb sentinel comments.
    delim1,delim2,delim3 = g.set_delims_from_language("c")
elif self.comment_string:
    delim1,delim2,delim3 = g.set_delims_from_string(self.comment_string)
elif self.language == "plain": # 1/30/03
    delim1,delim2,delim3 = None,None,None
else:
    delim1,delim2,delim3 = g.set_delims_from_language(self.language)

self.single_comment_start = delim1
self.block_comment_start = delim2
self.block_comment_end = delim3

# A strong case can be made for making this code as fast as possible.
# Whether this is compatible with general language descriptions remains to be seen.
self.case_sensitiveLanguage = self.language not in case_insensitiveLanguages
self.has_string = self.language != "plain"
if self.language == "plain":
    self.string_delims = ()
elif self.language in ("elisp","html"):
    self.string_delims = ('"')
else:
    self.string_delims = ("'",'"')
self.has_pp_directives = self.language in ("c","csharp","cweb","latex")

# The list of languages for which keywords exist.
# Eventually we might just use language_delims_dict.keys()
languages = [
    "actionscript","c","csharp","css","cweb","elisp","html","java","latex",
    "pascal","perl","perlpod","php","python","rapidq","rebol","shell","tcltk"]

self.keywords = []
if self.language == "cweb":
    for i in self.c_keywords:
        self.keywords.append(i)
    for i in self.cweb_keywords:
        self.keywords.append(i)
else:
    for name in languages:
        if self.language==name: 
            # g.trace("setting keywords for",name)
            self.keywords = getattr(self, name + "_keywords")

# Color plain text unless we are under the control of @nocolor.
# state = g.choose(self.flag,"normal","nocolor")
state = self.setFirstLineState()

if 1: # 10/25/02: we color both kinds of references in cweb mode.
    self.lb = "<<"
    self.rb = ">>"
else:
    self.lb = g.choose(self.language == "cweb","@<","<<")
    self.rb = g.choose(self.language == "cweb","@>",">>")
#@nonl
#@-node:ekr.20031218072017.370:<< configure language-specific settings >> colorizer
#@-node:ekr.20031218072017.1602:<< initialize ivars & tags >> colorizeAnyLanguage
#@+node:ekr.20031218072017.1881:<< all state ivars match >>
self.flag == self.last_flag and
self.last_language == self.language and
self.comment_string == self.last_comment and
self.markup_string == self.last_markup
#@nonl
#@-node:ekr.20031218072017.1881:<< all state ivars match >>
#@+node:ekr.20031218072017.1882:<< incrementally color the text >>
@  Each line has a starting state.  The starting state for the first line is always "normal".

We need remember only self.lines and self.states between colorizing.  It is not necessary to know where the text comes from, only what the previous text was!  We must always colorize everything when changing nodes, even if all lines match, because the context may be different.

We compute the range of lines to be recolored by comparing leading lines and trailing lines of old and new text.  All other lines (the middle lines) must be colorized, as well as any trailing lines whose states may have changed as the result of changes to the middle lines.
@c

# g.trace("incremental")

# 6/30/03: make a copies of everything
old_lines = self.lines[:]
old_states = self.states[:]
new_lines = lines[:]
new_states = []

new_len = len(new_lines)
old_len = len(old_lines)

if new_len == 0:
    self.states = []
    self.lines = []
    return

# Bug fix: 11/21/02: must test against None.
if leading != None and trailing != None:
    # print "leading,trailing:",leading,trailing
    leading_lines = leading
    trailing_lines = trailing
else:
    << compute leading, middle & trailing lines >>
    
middle_lines = new_len - leading_lines - trailing_lines
# print "middle lines", middle_lines

<< clear leading_lines if middle lines involve @color or @recolor  >>
<< initialize new states >>
<< colorize until the states match >>
#@nonl
#@+node:ekr.20031218072017.1883:<< compute leading, middle & trailing  lines >>
@ The leading lines are the leading matching lines.  The trailing lines are the trailing matching lines.  The middle lines are all other new lines.  We will color at least all the middle lines.  There may be no middle lines if we delete lines.
@c

min_len = min(old_len,new_len)

i = 0
while i < min_len:
    if old_lines[i] != new_lines[i]:
        break
    i += 1
leading_lines = i

if leading_lines == new_len:
    # All lines match, and we must color _everything_.
    # (several routine delete, then insert the text again,
    # deleting all tags in the process).
    # print "recolor all"
    leading_lines = trailing_lines = 0
else:
    i = 0
    while i < min_len - leading_lines:
        if old_lines[old_len-i-1] != new_lines[new_len-i-1]:
            break
        i += 1
    trailing_lines = i
#@-node:ekr.20031218072017.1883:<< compute leading, middle & trailing  lines >>
#@+node:ekr.20031218072017.1884:<< clear leading_lines if middle lines involve @color or @recolor  >>
@ 11/19/02: Changing @color or @nocolor directives requires we recolor all leading states as well.
@c

if trailing_lines == 0:
    m1 = new_lines[leading_lines:]
    m2 = old_lines[leading_lines:]
else:
    m1 = new_lines[leading_lines:-trailing_lines]
    m2 = old_lines[leading_lines:-trailing_lines]
m1.extend(m2) # m1 now contains all old and new middle lines.
if m1:
    for s in m1:
        s = g.toUnicode(s,g.app.tkEncoding) # 10/28/03
        i = g.skip_ws(s,0)
        if g.match_word(s,i,"@color") or g.match_word(s,i,"@nocolor"):
            leading_lines = 0
            break
#@-node:ekr.20031218072017.1884:<< clear leading_lines if middle lines involve @color or @recolor  >>
#@+node:ekr.20031218072017.1885:<< initialize new states >>
# Copy the leading states from the old to the new lines.
i = 0
while i < leading_lines and i < old_len: # 12/8/02
    new_states.append(old_states[i])
    i += 1
    
# We know the starting state of the first middle line!
if middle_lines > 0 and i < old_len:
    new_states.append(old_states[i])
    i += 1
    
# Set the state of all other middle lines to "unknown".
first_trailing_line = max(0,new_len - trailing_lines)
while i < first_trailing_line:
    new_states.append("unknown")
    i += 1

# Copy the trailing states from the old to the new lines.
i = max(0,old_len - trailing_lines)
while i < old_len and i < len(old_states):
    new_states.append(old_states[i])
    i += 1

# 1/8/03: complete new_states by brute force.
while len(new_states) < new_len:
    new_states.append("unknown")
#@nonl
#@-node:ekr.20031218072017.1885:<< initialize new states >>
#@+node:ekr.20031218072017.1886:<< colorize until the states match >>
# Colorize until the states match.
# All middle lines have "unknown" state, so they will all be colored.

# Start in the state _after_ the last leading line, which may be unknown.
i = leading_lines
while i > 0:
    if i < old_len and i < new_len:
        state = new_states[i]
        # assert(state!="unknown") # This can fail.
        break
    else:
        i -= 1

if i == 0:
    # Color plain text unless we are under the control of @nocolor.
    # state = g.choose(self.flag,"normal","nocolor")
    state = self.setFirstLineState()
    new_states[0] = state

# The new_states[] will be "unknown" unless the lines match,
# so we do not need to compare lines here.
while i < new_len:
    self.line_index = i + 1
    state = self.colorizeLine(new_lines[i],state)
    i += 1
    # Set the state of the _next_ line.
    if i < new_len and state != new_states[i]:
        new_states[i] = state
    else: break
    
# Update the ivars
self.states = new_states
self.lines = new_lines
#@nonl
#@-node:ekr.20031218072017.1886:<< colorize until the states match >>
#@-node:ekr.20031218072017.1882:<< incrementally color the text >>
#@+node:ekr.20031218072017.1887:<< non-incrementally color the text >>
# g.trace("non-incremental",self.language)

self.line_index = 1 # The Tk line number for indices, as in n.i
for s in lines:
    state = self.colorizeLine(s,state)
    self.line_index += 1
#@-node:ekr.20031218072017.1887:<< non-incrementally color the text >>
#@+node:ekr.20031218072017.1890:<< completely recolor in two passes >>
# This code is executed only if graphics characters will be inserted by user markup code.

# Pass 1:  Insert all graphics characters.

self.removeAllImages()
s = self.body.getAllText() # 10/27/03
lines = s.split('\n')

self.color_pass = 1
self.line_index = 1
state = self.setFirstLineState()
for s in lines:
    state = self.colorizeLine(s,state)
    self.line_index += 1

# Pass 2: Insert one blank for each previously inserted graphic.

self.color_pass = 2
self.line_index = 1
state = self.setFirstLineState()
for s in lines:
    << kludge: insert a blank in s for every image in the line >>
    state = self.colorizeLine(s,state)
    self.line_index += 1
#@+node:ekr.20031218072017.1891:<< kludge: insert a blank in s for every image in the line >>
@ A spectacular kludge.

Images take up a real index, yet the get routine does not return any character for them!
In order to keep the colorer in synch, we must insert dummy blanks in s at the positions corresponding to each image.
@c

inserted = 0

for photo,image,line_index,i in self.image_references:
    if self.line_index == line_index:
        n = i+inserted ; 	inserted += 1
        s = s[:n] + ' ' + s[n:]
#@-node:ekr.20031218072017.1891:<< kludge: insert a blank in s for every image in the line >>
#@-node:ekr.20031218072017.1890:<< completely recolor in two passes >>
#@+node:ekr.20031218072017.1888:<< update state ivars >>
self.last_flag = self.flag
self.last_language = self.language
self.last_comment = self.comment_string
self.last_markup = self.markup_string
#@nonl
#@-node:ekr.20031218072017.1888:<< update state ivars >>
#@+node:ekr.20031218072017.1889:<< set state ivars to "unknown" >>
self.last_flag = "unknown"
self.last_language = "unknown"
self.last_comment = "unknown"
#@nonl
#@-node:ekr.20031218072017.1889:<< set state ivars to "unknown" >>
#@-node:ekr.20031218072017.1880:colorizeAnyLanguage & allies
#@-node:ekr.20040910064225:(Removed assert from colorizer)
#@+node:ekr.20040913111703:(Fixed bugs in Tangle/Untangle)
#@+node:ekr.20040914074643:Report
@killcolor

http://sourceforge.net/forum/message.php?msg_id=2756978
By: nobody

The tangle menu options tangle all the @root nodes in a Leo file regardless
of the selection made (tangle m tangle marked, tangle all). In some of my files
Two of more will tangle togeather leaving others alone. Any suggestions.

The fix was to do p = p.copy in tangleTree, tanglePass1 and untangleTree.  untangleTree calls tanglePass1 to traverse the tree, so there are the only changes needed.
#@nonl
#@-node:ekr.20040914074643:Report
#@-node:ekr.20040913111703:(Fixed bugs in Tangle/Untangle)
#@+node:ekr.20040914081306:(Shifted all Alt-shortcuts to Ctrl shortcuts on the Mac)
#@+node:ekr.20040914081306.1:Report
@killcolor

http://sourceforge.net/forum/message.php?msg_id=2742238
By: hinsen

The shortcut definitions in Leo are quite inconvenient on the Mac, as those
using the Alt key don't work. Alt is used for entry of additional characters.
On the other hand, ctrl is available for shortcuts but not used in Leo, since
the ctrl-based shortcuts are shifted to the command key (following standard
Mac practice).

The patch at the end of this message simply shifts all Alt-shortcuts to Ctrl
on the Mac.

That leaves one minor clash: Command-` is used for CloneNode in Leo, although
it canonical MacOS function (switching between the windows of an application)
would be very useful in Leo as well. Removing the CloneNode binding is easy
enough, but it seems that I must implement a window switching function myself.
Not today :-)

Konrad.

#@-node:ekr.20040914081306.1:Report
#@+node:ekr.20031218072017.2098:canonicalizeShortcut
@ This code "canonicalizes" both the shortcuts that appear in menus and the arguments to bind, mostly ignoring case and the order in which special keys are specified in leoConfig.txt.

For example, Ctrl+Shift+a is the same as Shift+Control+A.  Either may appear in leoConfig.txt.  Each generates Shift+Ctrl-A in the menu and Control+A as the argument to bind.

Returns (bind_shortcut, menu_shortcut)
@c

def canonicalizeShortcut (self,shortcut):
    
    if shortcut == None or len(shortcut) == 0:
        return None,None
    s = shortcut.strip().lower()
    
    has_cmd   = s.find("cmd") >= 0     or s.find("command") >= 0 # 11/18/03
    has_ctrl  = s.find("control") >= 0 or s.find("ctrl") >= 0
    has_alt   = s.find("alt") >= 0
    has_shift = s.find("shift") >= 0   or s.find("shft") >= 0
    if sys.platform == "darwin":
        if has_ctrl and not has_cmd:
            has_cmd = True ; has_ctrl = False
        if has_alt and not has_ctrl: # 9/14/04
            has_ctrl = True ; has_alt = False
    << set the last field, preserving case >>
    << canonicalize the last field >>
    << synthesize the shortcuts from the information >>
    # print shortcut,bind_shortcut,menu_shortcut
    return bind_shortcut,menu_shortcut
#@nonl
#@+node:ekr.20031218072017.2102:<< set the last field, preserving case >>
s2 = shortcut
s2 = string.strip(s2)

# Replace all minus signs by plus signs, except a trailing minus:
if len(s2) > 0 and s2[-1] == "-":
    s2 = string.replace(s2,"-","+")
    s2 = s2[:-1] + "-"
else:
    s2 = string.replace(s2,"-","+")

fields = string.split(s2,"+")
if fields == None or len(fields) == 0:
    if not g.app.menuWarningsGiven:
        print "bad shortcut specifier:", s
    return None,None

last = fields[-1]
if last == None or len(last) == 0:
    if not g.app.menuWarningsGiven:
        print "bad shortcut specifier:", s
    return None,None
#@nonl
#@-node:ekr.20031218072017.2102:<< set the last field, preserving case >>
#@+node:ekr.20031218072017.2099:<< canonicalize the last field >>
bind_last = menu_last = last
if len(last) == 1:
    ch = last[0]
    if ch in string.ascii_letters:
        menu_last = string.upper(last)
        if has_shift:
            bind_last = string.upper(last)
        else:
            bind_last = string.lower(last)
    elif ch in string.digits:
        bind_last = "Key-" + ch # 1-5 refer to mouse buttons, not keys.
    else:
        << define dict of Tk bind names >>
        if ch in dict.keys():
            bind_last = dict[ch]
elif len(last) > 0:
    << define dict of special names >>
    last2 = string.lower(last)
    if last2 in dict.keys():
        bind_last,menu_last = dict[last2]
#@nonl
#@+node:ekr.20031218072017.2100:<< define dict of Tk bind names >>
# These are defined at http://tcl.activestate.com/man/tcl8.4/TkCmd/keysyms.htm.
dict = {
    "!" : "exclam",
    '"' : "quotedbl",
    "#" : "numbersign",
    "$" : "dollar",
    "%" : "percent",
    "&" : "ampersand",
    "'" : "quoteright",
    "(" : "parenleft",
    ")" : "parenright",
    "*" : "asterisk",
    "+" : "plus",
    "," : "comma",
    "-" : "minus",
    "." : "period",
    "/" : "slash",
    ":" : "colon",
    ";" : "semicolon",
    "<" : "less",
    "=" : "equal",
    ">" : "greater",
    "?" : "question",
    "@" : "at",
    "[" : "bracketleft",
    "\\": "backslash",
    "]" : "bracketright",
    "^" : "asciicircum",
    "_" : "underscore",
    "`" : "quoteleft",
    "{" : "braceleft",
    "|" : "bar",
    "}" : "braceright",
    "~" : "asciitilde" }
#@nonl
#@-node:ekr.20031218072017.2100:<< define dict of Tk bind names >>
#@+node:ekr.20031218072017.2101:<< define dict of special names >>
# These keys are simply made-up names.  The menu_bind values are known to Tk.
# Case is not significant in the keys.

dict = {
    "bksp"    : ("BackSpace","BkSp"),
    "esc"     : ("Escape","Esc"),
    # Arrow keys...
    "dnarrow" : ("Down", "DnArrow"),
    "ltarrow" : ("Left", "LtArrow"),
    "rtarrow" : ("Right","RtArrow"),
    "uparrow" : ("Up",   "UpArrow"),
    # Page up/down keys...
    "pageup"  : ("Prior","PgUp"),
    "pagedn"  : ("Next", "PgDn")
}

@  The following are not translated, so what appears in the menu is the same as what is passed to Tk.  Case is significant.

Note: the Tk documentation states that not all of these may be available on all platforms.

F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,
BackSpace, Break, Clear, Delete, Escape, Linefeed, Return, Tab,
Down, Left, Right, Up,
Begin, End, Home, Next, Prior,
Num_Lock, Pause, Scroll_Lock, Sys_Req,
KP_Add, KP_Decimal, KP_Divide, KP_Enter, KP_Equal,
KP_Multiply, KP_Separator,KP_Space, KP_Subtract, KP_Tab,
KP_F1,KP_F2,KP_F3,KP_F4,
KP_0,KP_1,KP_2,KP_3,KP_4,KP_5,KP_6,KP_7,KP_8,KP_9
#@-node:ekr.20031218072017.2101:<< define dict of special names >>
#@-node:ekr.20031218072017.2099:<< canonicalize the last field >>
#@+node:ekr.20031218072017.2103:<< synthesize the shortcuts from the information >>
bind_head = menu_head = ""

if has_shift:
    menu_head = "Shift+"
    if len(last) > 1 or (len(last)==1 and last[0] not in string.ascii_letters):
        bind_head = "Shift-"
if has_alt:
    bind_head = bind_head + "Alt-"
    menu_head = menu_head + "Alt+"

if has_ctrl:
    bind_head = bind_head + "Control-"
    menu_head = menu_head + "Ctrl+"
    
if has_cmd: # 11/18/03
    bind_head = bind_head + "Command-"
    menu_head = menu_head + "Command+"
    
bind_shortcut = "<" + bind_head + bind_last + ">"
menu_shortcut = menu_head + menu_last
#@nonl
#@-node:ekr.20031218072017.2103:<< synthesize the shortcuts from the information >>
#@-node:ekr.20031218072017.2098:canonicalizeShortcut
#@-node:ekr.20040914081306:(Shifted all Alt-shortcuts to Ctrl shortcuts on the Mac)
#@+node:ekr.20040914082436:(Added c.frame.openDirectory to sys.path when executing scripts)
#@+node:ekr.20031218072017.2140:c.executeScript
def executeScript(self,p=None,script=None):

    """This executes body text as a Python script.
    
    We execute the selected text, or the entire body text if no text is selected."""
    
    c = self ; error = False ; s = None ; script1 = script

    if not script:
        script = g.getScript(c,p)
    << redirect output >>
    if script:
        script = script.strip()
    if script:
        # 9/14/04: Temporarily add the open directory to sys.path.
        sys.path.insert(0,c.frame.openDirectory)
        script += '\n' # Make sure we end the script properly.
        try:
            exec script in {} # Use {} to get a pristine environment!
            << unredirect output >>
            if not script1:
                g.es("end of script",color="purple")
        except:
            << unredirect output >>
            g.es("exception executing script")
            n = g.es_exception(full=False,c=c)
            if n is not None:
                << dump the lines of script near the error >>
                if p and not script1:
                    c.goToScriptLineNumber(p,script,n)
            c.frame.tree.redrawAfterException()
        del sys.path[0]
    elif not error:
        << unredirect output >>
        g.es("no script selected",color="blue")
        
    # Force a redraw _after_ all messages have been output.
    c.redraw() 
#@nonl
#@+node:ekr.20031218072017.2143:<< redirect output >>
if g.app.config.redirect_execute_script_output_to_log_pane:

    g.redirectStdout() # Redirect stdout
    g.redirectStderr() # Redirect stderr
#@nonl
#@-node:ekr.20031218072017.2143:<< redirect output >>
#@+node:EKR.20040627100424:<< unredirect output >>
if g.app.config.redirect_execute_script_output_to_log_pane:

    g.restoreStderr()
    g.restoreStdout()
#@nonl
#@-node:EKR.20040627100424:<< unredirect output >>
#@+node:EKR.20040612215018:<< dump the lines of script near the error >>
lines = g.splitLines(script)

s = '-' * 20
print s; g.es(s)

if 1:
    # Just print the error line.
    s = "line %d: %s" % (n,lines[n-1])
    print s, ; g.es(s,newline=False)
else:
    i = max(0,n-2)
    j = min(n+2,len(lines))
    # g.trace(n,i,j)
    while i < j:
        ch = g.choose(i==n-1,'*',' ')
        s = "%s line %d: %s" % (ch,i+1,lines[i])
        print s, ; g.es(s,newline=False)
        i += 1
#@nonl
#@-node:EKR.20040612215018:<< dump the lines of script near the error >>
#@-node:ekr.20031218072017.2140:c.executeScript
#@-node:ekr.20040914082436:(Added c.frame.openDirectory to sys.path when executing scripts)
#@-node:ekr.20040809102904:4.2 rc1 projects
#@-all
#@nonl
#@-node:EKR.20040429143933:@thin leoProjects.txt
#@-leo
