leoEditCommands.py :  » Development » Leo » Leo-4.7.1-final » leo » core » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » Leo 
Leo » Leo 4.7.1 final » leo » core » leoEditCommands.py
# -*- coding: utf-8 -*-
#@+leo-ver=4-thin
#@+node:ekr.20050710142719:@thin leoEditCommands.py
#@@first

'''Basic editor commands for Leo.

Modelled after Emacs and Vim commands.'''

#@<< imports >>
#@+node:ekr.20050710151017:<< imports >>
import leo.core.leoGlobals as g
import leo.core.leoFind as leoFind
import leo.core.leoKeys as leoKeys
import leo.core.leoPlugins as leoPlugins
import leo.core.leoTest as leoTest

import ctypes
import ctypes.util
import difflib
import os
import re
import string   
import sys

if g.isPython3:
    import pickle # Only pickle exists in Python 3.x.
else:
    import cPickle as pickle 

if g.isPython3:
    from functools import reduce

subprocess = g.importExtension('subprocess',pluginName=None,verbose=False)
#@-node:ekr.20050710151017:<< imports >>
#@nl

#@<< define class baseEditCommandsClass >>
#@+node:ekr.20050920084036.1:<< define class baseEditCommandsClass >>
class baseEditCommandsClass:

    '''The base class for all edit command classes'''

    #@    @+others
    #@+node:ekr.20050920084036.2: ctor, finishCreate, init (baseEditCommandsClass)
    def __init__ (self,c):

        self.c = c
        self.k = self.k = None
        self.registers = {} # To keep pychecker happy.
        self.undoData = None

    def finishCreate(self):

        # Class delegators.
        self.k = self.k = self.c.k
        try:
            self.w = self.c.frame.body.bodyCtrl # New in 4.4a4.
        except AttributeError:
            self.w = None

    def init (self):

        '''Called from k.keyboardQuit to init all classes.'''

        pass
    #@nonl
    #@-node:ekr.20050920084036.2: ctor, finishCreate, init (baseEditCommandsClass)
    #@+node:ekr.20051214132256:begin/endCommand (baseEditCommands)
    #@+node:ekr.20051214133130:beginCommand  & beginCommandWithEvent
    def beginCommand (self,undoType='Typing'):

        '''Do the common processing at the start of each command.'''

        return self.beginCommandHelper(ch='',undoType=undoType,w=self.w)

    def beginCommandWithEvent (self,event,undoType='Typing'):

        '''Do the common processing at the start of each command.'''

        return self.beginCommandHelper(ch=event.char,undoType=undoType,w=event.widget)
    #@+node:ekr.20051215102349:beingCommandHelper
    # New in Leo 4.4b4: calling beginCommand is valid for all widgets,
    # but does nothing unless we are in the body pane.

    def beginCommandHelper (self,ch,undoType,w):

        c = self.c ; p = c.p
        name = c.widget_name(w)

        if name.startswith('body'):
            oldSel =  w.getSelectionRange()
            oldText = p.b
            self.undoData = b = g.Bunch()
            # To keep pylint happy.
            b.ch=ch
            b.name=name
            b.oldSel=oldSel
            b.oldText=oldText
            b.w=w
            b.undoType=undoType
        else:
            self.undoData = None

        return w
    #@-node:ekr.20051215102349:beingCommandHelper
    #@-node:ekr.20051214133130:beginCommand  & beginCommandWithEvent
    #@+node:ekr.20051214133130.1:endCommand
    # New in Leo 4.4b4: calling endCommand is valid for all widgets,
    # but handles undo only if we are in body pane.

    def endCommand(self,label=None,changed=True,setLabel=True):

        '''Do the common processing at the end of each command.'''

        c = self.c ; b = self.undoData ; k = self.k

        # g.trace('changed',changed)

        if b and b.name.startswith('body') and changed:
            c.frame.body.onBodyChanged(undoType=b.undoType,
                oldSel=b.oldSel,oldText=b.oldText,oldYview=None)

        self.undoData = None # Bug fix: 1/6/06 (after a5 released).

        k.clearState()

        # Warning: basic editing commands **must not** set the label.
        if setLabel:
            if label:
                k.setLabelGrey(label)
            else:
                k.resetLabel()
    #@-node:ekr.20051214133130.1:endCommand
    #@-node:ekr.20051214132256:begin/endCommand (baseEditCommands)
    #@+node:ekr.20061007105001:editWidget (baseEditCommandsClass)
    def editWidget (self,event):

        c = self.c ; w = event and event.widget

        # g.trace(w,g.app.gui.isTextWidget(w))

        # New in Leo 4.5: single-line editing commands apply to minibuffer widget.
        if w and g.app.gui.isTextWidget(w):
            self.w = w
        else:
            self.w = self.c.frame.body and self.c.frame.body.bodyCtrl

        if self.w:
            c.widgetWantsFocusNow(self.w)

        return self.w
    #@nonl
    #@-node:ekr.20061007105001:editWidget (baseEditCommandsClass)
    #@+node:ekr.20050920084036.5:getPublicCommands & getStateCommands
    def getPublicCommands (self):

        '''Return a dict describing public commands implemented in the subclass.
        Keys are untranslated command names.  Values are methods of the subclass.'''

        return {}
    #@-node:ekr.20050920084036.5:getPublicCommands & getStateCommands
    #@+node:ekr.20050920084036.6:getWSString
    def getWSString (self,s):

        return ''.join([g.choose(ch=='\t',ch,' ') for ch in s])
    #@-node:ekr.20050920084036.6:getWSString
    #@+node:ekr.20050920084036.7:oops
    def oops (self):

        g.pr("baseEditCommandsClass oops:",
            g.callers(),
            "must be overridden in subclass")
    #@-node:ekr.20050920084036.7:oops
    #@+node:ekr.20050929161635:Helpers
    #@+node:ekr.20050920084036.249:_chckSel
    def _chckSel (self,event,warning='no selection'):

        c = self.c ; k = self.k

        w = self.editWidget(event)

        val = w and w.hasSelection()

        if warning and not val:
            k.setLabelGrey(warning)

        return val
    #@-node:ekr.20050920084036.249:_chckSel
    #@+node:ekr.20050920084036.250:_checkIfRectangle
    def _checkIfRectangle (self,event):

        k = self.k ; key = event.keysym.lower()

        val = self.registers.get(key)

        if val and type(val) == type([]):
            k.clearState()
            k.setLabelGrey("Register contains Rectangle, not text")
            return True

        return False
    #@-node:ekr.20050920084036.250:_checkIfRectangle
    #@+node:ekr.20050920084036.233:getRectanglePoints
    def getRectanglePoints (self,w):

        c = self.c
        c.widgetWantsFocusNow(w)

        s = w.getAllText()
        i,j = w.getSelectionRange()
        r1,r2 = g.convertPythonIndexToRowCol(s,i)
        r3,r4 = g.convertPythonIndexToRowCol(s,j)

        return r1+1,r2,r3+1,r4
    #@-node:ekr.20050920084036.233:getRectanglePoints
    #@+node:ekr.20051002090441:keyboardQuit
    def keyboardQuit (self,event):

        '''Clear the state and the minibuffer label.'''

        return self.k.keyboardQuit(event)
    #@-node:ekr.20051002090441:keyboardQuit
    #@-node:ekr.20050929161635:Helpers
    #@-others
#@-node:ekr.20050920084036.1:<< define class baseEditCommandsClass >>
#@nl

#@+others
#@+node:ekr.20050924100713: Module level... (leoEditCommands)
#@+node:ekr.20050920084720:createEditCommanders (leoEditCommands module)
def createEditCommanders (c):

    '''Create edit classes in the commander.'''

    global classesList

    for name, theClass in classesList:
        theInstance = theClass(c)# Create the class.
        setattr(c,name,theInstance)
        # g.trace(name,theInstance)
#@-node:ekr.20050920084720:createEditCommanders (leoEditCommands module)
#@+node:ekr.20050922104731:finishCreateEditCommanders (leoEditCommands module)
def finishCreateEditCommanders (c):

    '''Finish creating edit classes in the commander.

    Return the commands dictionary for all the classes.'''

    global classesList

    d = {}

    for name, theClass in classesList:
        theInstance = getattr(c,name)
        theInstance.finishCreate()
        theInstance.init()
        d2 = theInstance.getPublicCommands()
        if d2:
            d.update(d2)
            if 0:
                g.pr('----- %s' % name)
                for key in sorted(d2): g.pr(key)

    return d
#@-node:ekr.20050922104731:finishCreateEditCommanders (leoEditCommands module)
#@+node:ekr.20050924100713.1:initAllEditCommanders
def initAllEditCommanders (c):

    '''Re-init classes in the commander.'''

    global classesList

    for name, theClass in classesList:
        theInstance = getattr(c,name)
        theInstance.init()
#@-node:ekr.20050924100713.1:initAllEditCommanders
#@-node:ekr.20050924100713: Module level... (leoEditCommands)
#@+node:ekr.20050920084036.13:abbrevCommandsClass (test)
#@+at
# 
# type some text, set its abbreviation with Control-x a i g, type 
# the text for abbreviation expansion
# type Control-x a e ( or Alt-x expand-abbrev ) to expand 
# abbreviation
# type Alt-x abbrev-on to turn on automatic abbreviation expansion
# Alt-x abbrev-on to turn it off
# 
# an example:
# type:
# frogs
# after typing 's' type Control-x a i g.  This will turn the 
# miniBuffer blue, type in your definition. For example: turtles.
# 
# Now in the buffer type:
# frogs
# after typing 's' type Control-x a e.  This will turn the 'frogs' 
# into:
# turtles
#@-at
#@@c

class abbrevCommandsClass (baseEditCommandsClass):

    #@    @+others
    #@+node:ekr.20050920084036.14: ctor & finishCreate
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        # Set local ivars.
        self.abbrevs ={}
        self.daRanges = []
        self.event = None
        self.dynaregex = re.compile(
            r'[%s%s\-_]+'%(string.ascii_letters,string.digits))
            # Not a unicode problem.
            # For dynamic abbreviations
        self.globalDynamicAbbrevs = c.config.getBool('globalDynamicAbbrevs')
        self.store ={'rlist':[], 'stext':''} # For dynamic expansion.
        self.w = None

    def finishCreate(self):

        baseEditCommandsClass.finishCreate(self)
    #@-node:ekr.20050920084036.14: ctor & finishCreate
    #@+node:ekr.20050920084036.15: getPublicCommands & getStateCommands
    def getPublicCommands (self):

        return {
            # 'expand-abbrev':              self.expandAbbrev, # Not a command.

            # Dynamic...
            'dabbrev-completion':           self.dynamicCompletion,
            'dabbrev-expands':              self.dynamicExpansion,

            # Static...
            'abbrev-mode':                  self.toggleAbbrevMode,
            'add-global-abbrev':            self.addAbbreviation,
            # 'expand-region-abbrevs':        self.regionalExpandAbbrev,
            'inverse-add-global-abbrev':    self.addInverseAbbreviation,
            'kill-all-abbrevs':             self.killAllAbbrevs,
            'list-abbrevs':                 self.listAbbrevs,
            'read-abbrev-file':             self.readAbbreviations,
            'write-abbrev-file':            self.writeAbbreviations,
        }
    #@-node:ekr.20050920084036.15: getPublicCommands & getStateCommands
    #@+node:ekr.20050920084036.58:dynamic abbreviation...
    #@+node:ekr.20050920084036.60:dynamicCompletion
    def dynamicCompletion (self,event=None):

        '''Insert the common prefix of all dynamic abbrev's matching the present word.
        This corresponds to C-M-/ in Emacs.'''

        c = self.c ; k = c.k ; p = c.p ; u = c.undoer
        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getWord(s,ins)
        txt = w.get(i,j)
        rlist = []
        self.getDynamicList(w,txt,rlist)
        if rlist:
            prefix = reduce(g.longestCommonPrefix,rlist)
            if prefix:
                b = c.undoer.beforeChangeNodeContents(c.p,oldBody=p.b,oldHead=p.h)
                w.delete(i,j)
                w.insert(i,prefix)
                p.b = w.getAllText()
                c.undoer.afterChangeNodeContents(p,
                    command='dabbrev-completion',bunch=b,dirtyVnodeList=[]) 
    #@nonl
    #@-node:ekr.20050920084036.60:dynamicCompletion
    #@+node:ekr.20050920084036.59:dynamicExpansion
    def dynamicExpansion (self,event=None):

        '''Expand the word in the buffer before point as a dynamic abbrev,
        by searching in the buffer for words starting with that abbreviation (dabbrev-expand).
        This corresponds to M-/ in Emacs.'''

        c = self.c ; k = c.k ; p = c.p ; u = c.undoer
        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getWord(s,ins)
        txt = w.get(i,j)
        rlist = []
        self.getDynamicList(w,txt,rlist)
        if not rlist: return
        prefix = reduce(g.longestCommonPrefix,rlist)
        if prefix and prefix != txt:
            b = c.undoer.beforeChangeNodeContents(c.p,oldBody=p.b,oldHead=p.h)
            w.delete(i,j)
            w.insert(i,prefix)
            p.b = w.getAllText()
            c.undoer.afterChangeNodeContents(p,
                command='dabbrev-expands',bunch=b,dirtyVnodeList=[])
        else:
            self.dynamicExpandHelper(prefix,rlist,w)
    #@+node:ekr.20070605110441:dynamicExpandHelper
    def dynamicExpandHelper (self,prefix=None,rlist=None,w=None):

        k = self.k ; tag = 'dabbrev-expand'
        state = k.getState(tag)

        if state == 0:
            self.w = w
            if w:
                names = rlist ; event = None
                prefix2 = 'dabbrev-expand: '
                k.setLabelBlue(prefix2+prefix,protect=True)
                k.getArg(event,tag,1,self.dynamicExpandHelper,prefix=prefix2,tabList=names)
        else:
            k.clearState()
            k.resetLabel()
            if k.arg:
                w = self.w
                s = w.getAllText()
                ins = w.getInsertPoint()
                i,j = g.getWord(s,ins)
                w.delete(i,j)
                w.insert(i,k.arg)
    #@nonl
    #@-node:ekr.20070605110441:dynamicExpandHelper
    #@-node:ekr.20050920084036.59:dynamicExpansion
    #@+node:ekr.20050920084036.61:getDynamicList (helper)
    def getDynamicList (self,w,txt,rlist):

        items = []
        if self.globalDynamicAbbrevs:
            for p in self.c.all_positions():
                s = p.b
                if s:
                    items.extend(self.dynaregex.findall(s))
        else:
            # Make a big list of what we are considering a 'word'
            s = w.getAllText()
            items.append(self.dynaregex.findall(s))

        # g.trace('txt',repr(txt),'len(items)',len(items))

        if items:
            for word in items:
                if not word.startswith(txt) or word == txt:
                    continue
                    # dont need words that dont match or == the pattern
                if word not in rlist:
                    rlist.append(word)
                else:
                    rlist.remove(word)
                    rlist.append(word)

        # g.trace('rlist',rlist)
    #@-node:ekr.20050920084036.61:getDynamicList (helper)
    #@-node:ekr.20050920084036.58:dynamic abbreviation...
    #@+node:ekr.20070531103114:static abbrevs
    #@+node:ekr.20050920084036.25:addAbbreviation
    def addAbbreviation (self,event):

        '''Add an abbreviation:
        The selected text is the abbreviation;
        the minibuffer prompts you for the name of the abbreviation.
        Also sets abbreviations on.'''

        k = self.k ; state = k.getState('add-abbr')

        if state == 0:
            w = self.editWidget(event) # Sets self.w
            if not w: return
            k.setLabelBlue('Add Abbreviation: ',protect=True)
            k.getArg(event,'add-abbr',1,self.addAbbreviation)
        else:
            w = self.w
            k.clearState()
            k.resetLabel()
            s = w.getAllText()
            i = w.getInsertPoint()
            i,j = g.getWord(s,i-1)
            word = s[i:j]
            if k.arg.strip():
                self.abbrevs [k.arg] = word
                k.abbrevOn = True
                k.setLabelGrey(
                    "Abbreviations are on.\nAbbreviation: '%s' = '%s'" % (
                    k.arg,word))
    #@-node:ekr.20050920084036.25:addAbbreviation
    #@+node:ekr.20051004080550:addInverseAbbreviation
    def addInverseAbbreviation (self,event):

        '''Add an inverse abbreviation:
        The selected text is the abbreviation name;
        the minibuffer prompts you for the value of the abbreviation.'''

        k = self.k ; state = k.getState('add-inverse-abbr')

        if state == 0:
            w = self.editWidget(event) # Sets self.w
            if not w: return
            k.setLabelBlue('Add Inverse Abbreviation: ',protect=True)
            k.getArg(event,'add-inverse-abbr',1,self.addInverseAbbreviation)
        else:
            w = self.w
            k.clearState()
            k.resetLabel()
            s = w.getAllText()
            i = w.getInsertPoint()
            i,j = g.getWord(s,i-1)
            word = s[i:j]
            if word:
                self.abbrevs [word] = k.arg
    #@-node:ekr.20051004080550:addInverseAbbreviation
    #@+node:ekr.20050920084036.27:expandAbbrev
    def expandAbbrev (self,event):

        '''Not a command.  Called from k.masterCommandtoexpand import 
        abbreviations in event.widget.'''

        k = self.k ; c = self.c ; ch = event.char.strip()
        w = self.editWidget(event)
        if not w: return

        ### word = w.get('insert -1c wordstart','insert -1c wordend') ###
        word = ''
        # g.trace('ch',repr(ch),'word',repr(word))
        if ch:
            # We must do this: expandAbbrev is called from Alt-x and Control-x,
            # we get two differnt types of data and w states.
            word = '%s%s'% (word,ch)

        val = self.abbrevs.get(word)
        if val is not None:
            s = w.getAllText()
            i = w.getInsertPoint()
            i,j = g.getWord(s,i-1)
            if i != j: w.delete(i,j)
            w.insert(i,val)
            c.frame.body.onBodyChanged(undoType='Typing')

        return val is not None
    #@-node:ekr.20050920084036.27:expandAbbrev
    #@+node:ekr.20050920084036.18:killAllAbbrevs
    def killAllAbbrevs (self,event):

        '''Delete all abbreviations.'''

        self.abbrevs = {}
    #@-node:ekr.20050920084036.18:killAllAbbrevs
    #@+node:ekr.20050920084036.19:listAbbrevs
    def listAbbrevs (self,event):

        '''List all abbreviations.'''

        k = self.k

        if self.abbrevs:
            for z in self.abbrevs:
                s = self.abbrevs[z]
                g.es('','%s=%s' % (z,s))
    #@-node:ekr.20050920084036.19:listAbbrevs
    #@+node:ekr.20050920084036.20:readAbbreviations
    def readAbbreviations (self,event):

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

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

        if not fileName: return

        try:
            f = open(fileName)
            for x in f:
                a, b = x.split('=')
                b = b [:-1]
                self.abbrevs [a] = b
            f.close()
        except IOError:
            g.es('can not open',fileName)
    #@-node:ekr.20050920084036.20:readAbbreviations
    #@+node:ekr.20050920084036.23:toggleAbbrevMode
    def toggleAbbrevMode (self,event):

        '''Toggle abbreviation mode.'''

        k = self.k
        k.abbrevOn = not k.abbrevOn
        k.keyboardQuit(event)
        k.setLabel('Abbreviations are ' + g.choose(k.abbrevOn,'On','Off'))
    #@-node:ekr.20050920084036.23:toggleAbbrevMode
    #@+node:ekr.20050920084036.24:writeAbbreviations
    def writeAbbreviations (self,event):

        '''Write abbreviations to a file.'''

        fileName = g.app.gui.runSaveFileDialog(
            initialfile = None,
            title='Write Abbreviations',
            filetypes = [("Text","*.txt"), ("All files","*")],
            defaultextension = ".txt")

        if not fileName: return

        try:
            f = open(fileName,'w')
            for x in self.abbrevs:
                f.write('%s=%s\n' % (x,self.abbrevs[x]))
            f.close()
        except IOError:
            g.es('can not create',fileName)
    #@-node:ekr.20050920084036.24:writeAbbreviations
    #@-node:ekr.20070531103114:static abbrevs
    #@-others
#@-node:ekr.20050920084036.13:abbrevCommandsClass (test)
#@+node:ekr.20050920084036.31:bufferCommandsClass
#@+at 
#@nonl
# An Emacs instance does not have knowledge of what is considered a 
# buffer in the environment.
# 
# The call to setBufferInteractionMethods calls the buffer 
# configuration methods.
#@-at
#@@c

class bufferCommandsClass (baseEditCommandsClass):

    #@    @+others
    #@+node:ekr.20050920084036.32: ctor (bufferCommandsClass)
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.fromName = '' # Saved name from getBufferName.
        self.nameList = [] # [n: <headline>]
        self.names = {}
        self.tnodes = {} # Keys are n: <headline>, values are tnodes.

        try:
            self.w = c.frame.body.bodyCtrl
        except AttributeError:
            self.w = None
    #@-node:ekr.20050920084036.32: ctor (bufferCommandsClass)
    #@+node:ekr.20050920084036.33: getPublicCommands
    def getPublicCommands (self):

        return {

            # These do not seem useful.
                # 'copy-to-buffer':               self.copyToBuffer,
                # 'insert-to-buffer':             self.insertToBuffer,

            'append-to-buffer':             self.appendToBuffer,
            'kill-buffer' :                 self.killBuffer,
            'list-buffers' :                self.listBuffers,
            'list-buffers-alphabetically':  self.listBuffersAlphabetically,
            'prepend-to-buffer':            self.prependToBuffer,
            'rename-buffer':                self.renameBuffer,
            'switch-to-buffer':             self.switchToBuffer,
        }
    #@-node:ekr.20050920084036.33: getPublicCommands
    #@+node:ekr.20050920084036.34:Entry points
    #@+node:ekr.20050920084036.35:appendToBuffer
    def appendToBuffer (self,event):

        '''Add the selected body text to the end of the body text of a named buffer (node).'''

        w = self.editWidget(event) # Sets self.w
        if not w: return

        self.k.setLabelBlue('Append to buffer: ')
        self.getBufferName(self.appendToBufferFinisher)

    def appendToBufferFinisher (self,name):

        c = self.c ; k = self.k ; w = self.w
        s = w.getSelectedText()
        p = self.findBuffer(name)
        if s and p:
            w = self.w
            c.selectPosition(p)
            self.beginCommand('append-to-buffer: %s' % p.h)
            w.insert('end',s)
            w.setInsertPoint('end')
            w.seeInsertPoint()
            self.endCommand()
            c.redraw_after_icons_changed()
            c.recolor()
    #@nonl
    #@-node:ekr.20050920084036.35:appendToBuffer
    #@+node:ekr.20050920084036.36:copyToBuffer
    def copyToBuffer (self,event):

        '''Add the selected body text to the end of the body text of a named buffer (node).'''

        w = self.editWidget(event) # Sets self.w
        if not w: return

        self.k.setLabelBlue('Copy to buffer: ')
        self.getBufferName(self.copyToBufferFinisher)

    def copyToBufferFinisher (self,event,name):

        c = self.c ; k = self.k ; w = self.w
        s = w.getSelectedText()
        p = self.findBuffer(name)
        if s and p:
            c.selectPosition(p)
            self.beginCommand('copy-to-buffer: %s' % p.h)
            w.insert('end',s)
            w.setInsertPoint('end')
            self.endCommand()
            c.redraw_after_icons_changed()
            c.recolor()
    #@-node:ekr.20050920084036.36:copyToBuffer
    #@+node:ekr.20050920084036.37:insertToBuffer
    def insertToBuffer (self,event):

        '''Add the selected body text at the insert point of the body text of a named buffer (node).'''

        w = self.editWidget(event) # Sets self.w
        if not w: return

        self.k.setLabelBlue('Insert to buffer: ')
        self.getBufferName(self.insertToBufferFinisher)

    def insertToBufferFinisher (self,event,name):

        c = self.c ; k = self.k ; w = self.w
        s = w.getSelectedText()
        p = self.findBuffer(name)
        if s and p:
            c.selectPosition(p)
            self.beginCommand('insert-to-buffer: %s' % p.h)
            i = w.getInsertPoint()
            w.insert(i,s)
            w.seeInsertPoint()
            self.endCommand()
            c.redraw_after_icons_changed()
    #@nonl
    #@-node:ekr.20050920084036.37:insertToBuffer
    #@+node:ekr.20050920084036.38:killBuffer
    def killBuffer (self,event):

        '''Delete a buffer (node) and all its descendants.'''

        w = self.editWidget(event) # Sets self.w
        if not w: return

        self.k.setLabelBlue('Kill buffer: ')
        self.getBufferName(self.killBufferFinisher)

    def killBufferFinisher (self,name):

        c = self.c ; p = self.findBuffer(name)
        if p:
            h = p.h
            current = c.p
            c.selectPosition(p)
            c.deleteOutline (op_name='kill-buffer: %s' % h)
            c.selectPosition(current)
            self.k.setLabelBlue('Killed buffer: %s' % h)
            c.redraw(current)
    #@-node:ekr.20050920084036.38:killBuffer
    #@+node:ekr.20050920084036.42:listBuffers & listBuffersAlphabetically
    def listBuffers (self,event):

        '''List all buffers (node headlines), in outline order.
        Nodes with the same headline are disambiguated by giving their parent or child index.
        '''

        self.computeData()
        g.es('buffers...')
        for name in self.nameList:
            g.es('',name)

    def listBuffersAlphabetically (self,event):

        '''List all buffers (node headlines), in alphabetical order.
        Nodes with the same headline are disambiguated by giving their parent or child index.'''

        self.computeData()
        names = self.nameList[:] ; names.sort()

        g.es('buffers...')
        for name in names:
            g.es('',name)
    #@-node:ekr.20050920084036.42:listBuffers & listBuffersAlphabetically
    #@+node:ekr.20050920084036.39:prependToBuffer
    def prependToBuffer (self,event):

        '''Add the selected body text to the start of the body text of a named buffer (node).'''

        w = self.editWidget(event) # Sets self.w
        if not w: return

        self.k.setLabelBlue('Prepend to buffer: ')
        self.getBufferName(self.prependToBufferFinisher)

    def prependToBufferFinisher (self,event,name):

        c = self.c ; k = self.k ; w = self.w
        s = w.getSelectedText()
        p = self.findBuffer(name)
        if s and p:
            c.selectPosition(p)
            self.beginCommand('prepend-to-buffer: %s' % p.h)
            w.insert(0,s)
            w.setInsertPoint(0)
            w.seeInsertPoint()
            self.endCommand()
            c.redraw_after_icons_changed()
            c.recolor()
    #@-node:ekr.20050920084036.39:prependToBuffer
    #@+node:ekr.20050920084036.43:renameBuffer
    def renameBuffer (self,event):

        '''Rename a buffer, i.e., change a node's headline.'''

        self.k.setLabelBlue('Rename buffer from: ')
        self.getBufferName(self.renameBufferFinisher1)

    def renameBufferFinisher1 (self,name):

        self.fromName = name
        self.k.setLabelBlue('Rename buffer from: %s to: ' % (name))
        self.getBufferName(self.renameBufferFinisher2)

    def renameBufferFinisher2 (self,name):

        c = self.c ; p = self.findBuffer(self.fromName)
        if p:
            c.endEditing()
            c.setHeadString(p,name)
            c.redraw(p)
    #@-node:ekr.20050920084036.43:renameBuffer
    #@+node:ekr.20050920084036.40:switchToBuffer
    def switchToBuffer (self,event):

        '''Select a buffer (node) by its name (headline).'''

        self.k.setLabelBlue('Switch to buffer: ')
        self.getBufferName(self.switchToBufferFinisher)

    def switchToBufferFinisher (self,name):

        c = self.c ; p = self.findBuffer(name)
        if p:
            c.selectPosition(p)
            c.redraw_after_select(p)

    #@-node:ekr.20050920084036.40:switchToBuffer
    #@-node:ekr.20050920084036.34:Entry points
    #@+node:ekr.20050927102133.1:Utils
    #@+node:ekr.20051215121416:computeData
    def computeData (self):

        self.nameList = []
        self.names = {} ; self.tnodes = {}

        for p in self.c.all_unique_positions():
            h = p.h.strip()
            v = p.v
            nameList = self.names.get(h,[])
            if nameList:
                if p.parent():
                    key = '%s, parent: %s' % (h,p.parent().h)
                else:
                    key = '%s, child index: %d' % (h,p.childIndex())
            else:
                key = h
            self.nameList.append(key)
            self.tnodes[key] = v
            nameList.append(key)
            self.names[h] = nameList
    #@-node:ekr.20051215121416:computeData
    #@+node:ekr.20051215164823:findBuffer
    def findBuffer (self,name):

        v = self.tnodes.get(name)

        for p in self.c.all_unique_positions():
            if p.v == v:
                return p

        g.trace("Can't happen",name)
        return None
    #@-node:ekr.20051215164823:findBuffer
    #@+node:ekr.20050927093851:getBufferName
    def getBufferName (self,finisher):

        '''Get a buffer name into k.arg and call k.setState(kind,n,handler).'''

        k = self.k ; c = k.c ; state = k.getState('getBufferName')

        if state == 0:
            self.computeData()
            self.getBufferNameFinisher = finisher
            prefix = k.getLabel() ; event = None
            k.getArg(event,'getBufferName',1,self.getBufferName,
                prefix=prefix,tabList=self.nameList)
        else:
            k.resetLabel()
            k.clearState()
            finisher = self.getBufferNameFinisher
            self.getBufferNameFinisher = None
            finisher(k.arg)
    #@-node:ekr.20050927093851:getBufferName
    #@-node:ekr.20050927102133.1:Utils
    #@-others
#@-node:ekr.20050920084036.31:bufferCommandsClass
#@+node:ekr.20070522085324:chapterCommandsClass
class chapterCommandsClass (baseEditCommandsClass):

    #@    @+others
    #@+node:ekr.20070522085340: ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        # c.chapterController does not exist yet.
    #@-node:ekr.20070522085340: ctor
    #@+node:ekr.20070522085429: getPublicCommands (chapterCommandsClass)
    def getPublicCommands (self):

        c = self.c ; cc = c.chapterController

        # g.trace('cc',cc,g.callers())

        if cc:
            if 1: # new names
                return {
                    'chapter-clone-node-to':    cc.cloneNodeToChapter,
                    'chapter-convert-node-to':  cc.convertNodeToChapter,
                    'chapter-copy-node-to':     cc.copyNodeToChapter,
                    'chapter-create':           cc.createChapter,
                    'chapter-create-from-node': cc.createChapterFromNode,
                    'chapter-move-node-to':     cc.moveNodeToChapter,
                    'chapter-remove':           cc.removeChapter,
                    'chapter-rename':           cc.renameChapter,
                    'chapter-select':           cc.selectChapter,
                }
            else:
                return {
                    'clone-node-to-chapter':    cc.cloneNodeToChapter,
                    'convert-node-to-chapter':  cc.convertNodeToChapter,
                    'copy-node-to-chapter':     cc.copyNodeToChapter,
                    'create-chapter':           cc.createChapter,
                    'create-chapter-from-node': cc.createChapterFromNode,
                    'move-node-to-chapter':     cc.moveNodeToChapter,
                    'remove-chapter':           cc.removeChapter,
                    'rename-chapter':           cc.renameChapter,
                    'select-chapter':           cc.selectChapter,
                }
        else:
            return {}
    #@-node:ekr.20070522085429: getPublicCommands (chapterCommandsClass)
    #@-others
#@-node:ekr.20070522085324:chapterCommandsClass
#@+node:ekr.20050920084036.150:controlCommandsClass
class controlCommandsClass (baseEditCommandsClass):

    #@    @+others
    #@+node:ekr.20050920084036.151: ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.payload = None
    #@-node:ekr.20050920084036.151: ctor
    #@+node:ekr.20050920084036.152: getPublicCommands
    def getPublicCommands (self):

        k = self.c.k

        return {
            'advertised-undo':              self.advertizedUndo,
            'iconify-frame':                self.iconifyFrame, # Same as suspend.
            'keyboard-quit':                k and k.keyboardQuit,
            'save-buffers-kill-leo':        self.saveBuffersKillLeo,
            'set-silent-mode':              self.setSilentMode,
            # 'print-plugins':                self.printPlugins,
            'print-plugin-handlers':        self.printPluginHandlers,
            'print-plugins-info':           self.printPluginsInfo,
            'shell-command':                self.shellCommand,
            'shell-command-on-region':      self.shellCommandOnRegion,
            'suspend':                      self.suspend,
            'act-on-node':                  self.actOnNode
        }
    #@-node:ekr.20050920084036.152: getPublicCommands
    #@+node:ekr.20050922110030:advertizedUndo
    def advertizedUndo (self,event):

        '''Undo the previous command.'''

        self.c.undoer.undo()
    #@-node:ekr.20050922110030:advertizedUndo
    #@+node:ekr.20050920084036.160:executeSubprocess
    def executeSubprocess (self,event,command,theInput=None):

        '''Execute a command in a separate process.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        k.setLabelBlue('started  shell-command: %s' % command)
        try:
            ofile = os.tmpfile()
            efile = os.tmpfile()
            process = subprocess.Popen(command,bufsize=-1,
                stdout = ofile.fileno(), stderr = ofile.fileno(),
                stdin = subprocess.PIPE, shell = True)
            if theInput: process.communicate(theInput)
            process.wait()
            efile.seek(0)
            errinfo = efile.read()
            if errinfo:
                i = w.getInsertPoint()
                w.insert(i,errinfo)
            ofile.seek(0)
            okout = ofile.read()
            if okout:
                i = w.getInsertPoint()
                w.insert(i,okout)
        except Exception:
            junk, x, junk = sys.exc_info()
            i = w.getInsertPoint()
            w.insert(i,x)

        k.setLabelGrey('finished shell-command: %s' % command)
    #@-node:ekr.20050920084036.160:executeSubprocess
    #@+node:ekr.20070429090859:print plugins info...
    def printPluginHandlers (self,event=None):

        leoPlugins.printHandlers(self.c)

    def printPlugins (self,event=None):

        leoPlugins.printPlugins(self.c)

    def printPluginsInfo (self,event=None):

        leoPlugins.printPluginsInfo(self.c)
    #@-node:ekr.20070429090859:print plugins info...
    #@+node:ekr.20060603161041:setSilentMode
    def setSilentMode (self,event=None):

        '''Set the mode to be run silently, without the minibuffer.
        The only use for this command is to put the following in an @mode node::

            --> set-silent-mode'''

        self.c.k.silentMode = True
    #@-node:ekr.20060603161041:setSilentMode
    #@+node:ekr.20050920084036.158:shellCommand
    def shellCommand (self,event):

        '''Execute a shell command.'''

        if subprocess:
            k = self.k ; state = k.getState('shell-command')

            if state == 0:
                k.setLabelBlue('shell-command: ',protect=True)
                k.getArg(event,'shell-command',1,self.shellCommand)
            else:
                command = k.arg
                k.commandName = 'shell-command: %s' % command
                k.clearState()
                self.executeSubprocess(event,command)
        else:
            k.setLabelGrey('can not execute shell-command: can not import subprocess')
    #@-node:ekr.20050920084036.158:shellCommand
    #@+node:ekr.20050930112126:shellCommandOnRegion
    def shellCommandOnRegion (self,event):

        '''Execute a command taken from the selected text in a separate process.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        if subprocess:
            if w.hasSelection():
                command = w.getSelectedText()
                k.commandName = 'shell-command: %s' % command
                self.executeSubprocess(event,command)
            else:
                k.clearState()
                k.resetLabel()
        else:
            k.setLabelGrey('can not execute shell-command: can not import subprocess')
    #@-node:ekr.20050930112126:shellCommandOnRegion
    #@+node:ville.20090222184600.2:actOnNode
    def actOnNode(self, event):
        """ Execute node-specific action (typically defined by plugins)

        Actual behaviour is to be defined by plugins.

        Here's how to define actions for nodes in your plugins::

            import leo.core.leoPlugins
            def act_print_upcase(c,p,event):
                if not p.h.startswith('@up'):
                    raise leo.core.leoPlugins.TryNext
                p.h = p.h.upper()

            g.act_on_node.add(act_print_upcase)        

        This will upcase the headline when it starts with @up.            

        """
        g.act_on_node(self.c,self.c.p,event)
    #@-node:ville.20090222184600.2:actOnNode
    #@+node:ekr.20050920084036.155:shutdown, saveBuffersKillEmacs & setShutdownHook
    def shutdown (self,event):

        '''Quit Leo, prompting to save any unsaved files first.'''

        g.app.onQuit()

    saveBuffersKillLeo = shutdown
    #@-node:ekr.20050920084036.155:shutdown, saveBuffersKillEmacs & setShutdownHook
    #@+node:ekr.20050920084036.153:suspend & iconifyFrame
    def suspend (self,event):

        '''Minimize the present Leo window.'''

        w = self.editWidget(event)
        if not w: return
        self.c.frame.top.iconify()

    # Must be a separate function so that k.inverseCommandsDict will be a true inverse.

    def iconifyFrame (self,event):

        '''Minimize the present Leo window.'''

        self.suspend(event)
    #@-node:ekr.20050920084036.153:suspend & iconifyFrame
    #@-others
#@-node:ekr.20050920084036.150:controlCommandsClass
#@+node:ekr.20060127162818.1:debugCommandsClass
class debugCommandsClass (baseEditCommandsClass):

    #@    @+others
    #@+node:ekr.20060127162921: ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.
    #@-node:ekr.20060127162921: ctor
    #@+node:ekr.20060127163325: getPublicCommands
    def getPublicCommands (self):

        return {
            'collect-garbage':              self.collectGarbage,
            'debug':                        self.debug,
            'disable-gc-trace':             self.disableGcTrace,
            'dump-all-objects':             self.dumpAllObjects,
            'dump-new-objects':             self.dumpNewObjects,
            'enable-gc-trace':              self.enableGcTrace,
            'free-tree-widgets':            self.freeTreeWidgets,
            'pdb':                          self.pdb,
            'print-focus':                  self.printFocus,
            'print-stats':                  self.printStats,
            'print-gc-summary':             self.printGcSummary,
            'run-all-unit-tests':           self.runAllUnitTests, # The new way...
            'run-unit-tests':               self.runUnitTests,
            'run-all-unit-tests-locally':   self.runAllUnitTestsLocally, # The old way...
            'run-unit-tests-locally':       self.runUnitTestsLocally,
            'verbose-dump-objects':         self.verboseDumpObjects,
        }
    #@-node:ekr.20060127163325: getPublicCommands
    #@+node:ekr.20060205050659:collectGarbage
    def collectGarbage (self,event=None):

        """Run Python's Gargabe Collector."""

        g.collectGarbage()
    #@-node:ekr.20060205050659:collectGarbage
    #@+node:ekr.20060519003651:debug & helper
    def debug (self,event=None):

        '''Start an external debugger in another process to debug a script.
        The script is the presently selected text or then entire tree's script.'''

        c = self.c ; p = c.p
        python = sys.executable
        script = g.getScript(c,p)
        winpdb = self.findDebugger()
        if not winpdb: return

        #check for doctest examples
        try:
            import doctest
            parser = doctest.DocTestParser()
            examples = parser.get_examples(script)

            # if this is doctest, extract the examples as a script
            if len(examples) > 0:
                script = doctest.script_from_examples(script)
        except ImportError:
            pass

        # special case; debug code may include g.es("info string").
        # insert code fragment to make this expression legal outside Leo.
        hide_ges = "class G:\n def es(s,c=None):\n  pass\ng = G()\n"
        script = hide_ges + script

        # Create a temp file from the presently selected node.
        filename = c.writeScriptFile(script)
        if not filename: return

        # Invoke the debugger, retaining the present environment.
        os.chdir(g.app.loadDir)
        if False and subprocess:
            cmdline = '%s %s -t %s' % (python,winpdb,filename)
            subprocess.Popen(cmdline)
        else:
            args = [sys.executable, winpdb, '-t', filename]
            os.spawnv(os.P_NOWAIT, python, args)
    #@+node:ekr.20060521140213:findDebugger
    def findDebugger (self):

        '''Find the debugger using settings.'''

        c = self.c
        pythonDir = g.os_path_dirname(sys.executable)

        debuggers = (
            c.config.getString('debugger_path'),
            g.os_path_join(pythonDir,'Lib','site-packages','winpdb.py'), # winpdb 1.1.2 or newer
            g.os_path_join(pythonDir,'scripts','_winpdb.py'), # oder version.
        )

        for debugger in debuggers:
            if debugger:
                debugger = c.os_path_finalize(debugger)
                if g.os_path_exists(debugger):
                    return debugger
                else:
                    g.es('debugger does not exist:',debugger,color='blue')
        else:
            g.es('no debugger found.')
            return None
    #@-node:ekr.20060521140213:findDebugger
    #@-node:ekr.20060519003651:debug & helper
    #@+node:ekr.20060202160523:dumpAll/New/VerboseObjects
    def dumpAllObjects (self,event=None):

        '''Print a summary of all existing Python objects.'''

        old = g.app.trace_gc
        g.app.trace_gc = True
        g.printGcAll()
        g.app.trace_gc = old

    def dumpNewObjects (self,event=None):

        '''Print a summary of all Python objects created
        since the last time Python's Garbage collector was run.'''

        old = g.app.trace_gc
        g.app.trace_gc = True
        g.printGcObjects()
        g.app.trace_gc = old

    def verboseDumpObjects (self,event=None):

        '''Print a more verbose listing of all existing Python objects.'''

        old = g.app.trace_gc
        g.app.trace_gc = True
        g.printGcVerbose()
        g.app.trace_gc = old
    #@-node:ekr.20060202160523:dumpAll/New/VerboseObjects
    #@+node:ekr.20060127163325.1:enable/disableGcTrace
    def disableGcTrace (self,event=None):

        '''Enable tracing of Python's Garbage Collector.'''

        g.app.trace_gc = False


    def enableGcTrace (self,event=None):

        '''Disable tracing of Python's Garbage Collector.'''

        g.app.trace_gc = True
        g.enable_gc_debug()

        if g.app.trace_gc_verbose:
            g.es('enabled verbose gc stats',color='blue')
        else:
            g.es('enabled brief gc stats',color='blue')
    #@-node:ekr.20060127163325.1:enable/disableGcTrace
    #@+node:ekr.20060202154734:freeTreeWidgets
    def freeTreeWidgets (self,event=None):

        '''Free all widgets used in Leo's outline pane.'''

        c = self.c

        c.frame.tree.destroyWidgets()
        c.redraw()
    #@-node:ekr.20060202154734:freeTreeWidgets
    #@+node:ekr.20090226080753.8:pdb
    def pdb (self,event=None):

        g.pdb()
    #@nonl
    #@-node:ekr.20090226080753.8:pdb
    #@+node:ekr.20060210100432:printFocus
    # Doesn't work if the focus isn't in a pane with bindings!

    def printFocus (self,event=None):

        '''Print information about the requested focus (for debugging).'''

        c = self.c

        g.es_print('      hasFocusWidget:',c.widget_name(c.hasFocusWidget))
        g.es_print('requestedFocusWidget:',c.widget_name(c.requestedFocusWidget))
        g.es_print('           get_focus:',c.widget_name(c.get_focus()))
    #@-node:ekr.20060210100432:printFocus
    #@+node:ekr.20060205043324.3:printGcSummary
    def printGcSummary (self,event=None):

        '''Print a brief summary of all Python objects.'''

        g.printGcSummary()
    #@-node:ekr.20060205043324.3:printGcSummary
    #@+node:ekr.20060202133313:printStats
    def printStats (self,event=None):

        '''Print statistics about the objects that Leo is using.'''

        c = self.c
        c.frame.tree.showStats()
        self.dumpAllObjects()
    #@-node:ekr.20060202133313:printStats
    #@+node:ekr.20060328121145:runUnitTest commands
    def runAllUnitTestsLocally (self,event=None):
        '''Run all unit tests contained in the presently selected outline.
        Tests are run in the outline's process, so tests *can* change the outline.'''
        c = self.c
        leoTest.doTests(c,all=True)

    def runUnitTestsLocally (self,event=None):
        '''Run all unit tests contained in the presently selected outline.
        Tests are run in the outline's process, so tests *can* change the outline.'''
        c = self.c
        leoTest.doTests(c,all=False)

    def runAllUnitTests (self,event=None):
        '''Run all unit tests contained in the entire outline.
        Tests are run in an external process, so tests *cannot* change the outline.'''
        c = self.c
        leoTest.runTestsExternally(c,all=True)

    def runUnitTests(self,event=None):
        '''Run all unit tests contained in the presently selected outline
        Tests are run in an external process, so tests *cannot* change the outline.'''
        c = self.c
        leoTest.runTestsExternally(c,all=False)
    #@-node:ekr.20060328121145:runUnitTest commands
    #@-others
#@-node:ekr.20060127162818.1:debugCommandsClass
#@+node:ekr.20050920084036.53:editCommandsClass
class editCommandsClass (baseEditCommandsClass):

    '''Contains editing commands with little or no state.'''

    #@    @+others
    #@+node:ekr.20050929155208: birth
    #@+node:ekr.20050920084036.54: ctor (editCommandsClass)
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.ccolumn = '0'   # For comment column functions.
        self.extendMode = False # True: all cursor move commands extend the selection.
        self.fillPrefix = '' # For fill prefix functions.
        self.fillColumn = 70 # For line centering.
        self.moveSpotNode = None # A vnode.
        self.moveSpot = None # For retaining preferred column when moving up or down.
        self.moveCol = None # For retaining preferred column when moving up or down.
        self.sampleWidget = None # Created later.
        self.swapSpots = []
        self._useRegex = False # For replace-string
        self.w = None # For use by state handlers.

        # Settings...
        cf = c.config
        self.autocompleteBrackets   = cf.getBool('autocomplete-brackets')
        self.bracketsFlashBg        = cf.getColor('flash-brackets-background-color')
        self.bracketsFlashCount     = cf.getInt('flash-brackets-count')
        self.bracketsFlashDelay     = cf.getInt('flash-brackets-delay')
        self.bracketsFlashFg        = cf.getColor('flash-brackets-foreground-color')
        self.flashMatchingBrackets  = cf.getBool('flash-matching-brackets')
        self.smartAutoIndent        = cf.getBool('smart_auto_indent')
        self.openBracketsList       = cf.getString('open_flash_brackets')  or '([{'
        self.closeBracketsList      = cf.getString('close_flash_brackets') or ')]}'

        self.initBracketMatcher(c)
    #@-node:ekr.20050920084036.54: ctor (editCommandsClass)
    #@+node:ekr.20050920084036.55: getPublicCommands (editCommandsClass)
    def getPublicCommands (self):        

        c = self.c

        return {
            'activate-cmds-menu':                   self.activateCmdsMenu,
            'activate-edit-menu':                   self.activateEditMenu,
            'activate-file-menu':                   self.activateFileMenu,
            'activate-help-menu':                   self.activateHelpMenu,
            'activate-outline-menu':                self.activateOutlineMenu,
            'activate-plugins-menu':                self.activatePluginsMenu,
            'activate-window-menu':                 self.activateWindowMenu,
            'add-editor':                           c.frame.body and c.frame.body.addEditor,
            'add-space-to-lines':                   self.addSpaceToLines,
            'add-tab-to-lines':                     self.addTabToLines, 
            'back-to-indentation':                  self.backToIndentation,
            'back-to-home':                         self.backToHome,
            'back-char':                            self.backCharacter,
            'back-char-extend-selection':           self.backCharacterExtendSelection,
            'back-page':                            self.backPage,
            'back-page-extend-selection':           self.backPageExtendSelection,
            'back-paragraph':                       self.backwardParagraph,
            'back-paragraph-extend-selection':      self.backwardParagraphExtendSelection,
            'back-sentence':                        self.backSentence,
            'back-sentence-extend-selection':       self.backSentenceExtendSelection,
            'back-word':                            self.backwardWord,
            'back-word-extend-selection':           self.backwardWordExtendSelection,
            'backward-delete-char':                 self.backwardDeleteCharacter,
            'backward-kill-paragraph':              self.backwardKillParagraph,
            'backward-find-character':              self.backwardFindCharacter,
            'backward-find-character-extend-selection': self.backwardFindCharacterExtendSelection,
            'beginning-of-buffer':                  self.beginningOfBuffer,
            'beginning-of-buffer-extend-selection': self.beginningOfBufferExtendSelection,
            'beginning-of-line':                    self.beginningOfLine,
            'beginning-of-line-extend-selection':   self.beginningOfLineExtendSelection,
            'capitalize-word':                      self.capitalizeWord,
            'center-line':                          self.centerLine,
            'center-region':                        self.centerRegion,
            'clean-all-lines':                      self.cleanAllLines,
            'clean-lines':                          self.cleanLines,
            'clear-all-caches':                     self.clearAllCaches,
            'clear-cache':                          self.clearCache,
            'clear-extend-mode':                    self.clearExtendMode,
            'clear-selected-text':                  self.clearSelectedText,
            'click-click-box':                      self.clickClickBox,
            'click-headline':                       self.clickHeadline,
            'click-icon-box':                       self.clickIconBox,
            'clone-marked':                         c.cloneMarked,
            'contract-body-pane':                   c.frame.contractBodyPane,
            'contract-log-pane':                    c.frame.contractLogPane,
            'contract-outline-pane':                c.frame.contractOutlinePane,
            'contract-pane':                        c.frame.contractPane,
            'count-region':                         self.countRegion,
            'cycle-focus':                          self.cycleFocus,
            'cycle-all-focus':                      self.cycleAllFocus,
            'cycle-editor-focus':                   c.frame.body.cycleEditorFocus,
            # 'delete-all-icons':                   self.deleteAllIcons,
            'delete-char':                          self.deleteNextChar,
            'delete-editor':                        c.frame.body.deleteEditor,
            'delete-first-icon':                    self.deleteFirstIcon,
            'delete-indentation':                   self.deleteIndentation,
            'delete-last-icon':                     self.deleteLastIcon,
            'delete-node-icons':                    self.deleteNodeIcons,
            'delete-spaces':                        self.deleteSpaces,
            'do-nothing':                           self.doNothing,
            'downcase-region':                      self.downCaseRegion,
            'downcase-word':                        self.downCaseWord,
            'double-click-headline':                self.doubleClickHeadline,
            'double-click-icon-box':                self.doubleClickIconBox,
            'end-of-buffer':                        self.endOfBuffer,
            'end-of-buffer-extend-selection':       self.endOfBufferExtendSelection,
            'end-of-line':                          self.endOfLine,
            'end-of-line-extend-selection':         self.endOfLineExtendSelection,
            'escape':                               self.watchEscape,
            'eval-expression':                      self.evalExpression,
            'exchange-point-mark':                  self.exchangePointMark,
            'expand-body-pane':                     c.frame.expandBodyPane,
            'expand-log-pane':                      c.frame.expandLogPane,
            'expand-outline-pane':                  c.frame.expandOutlinePane,
            'expand-pane':                          c.frame.expandPane,
            'extend-to-line':                       self.extendToLine,
            'extend-to-paragraph':                  self.extendToParagraph,
            'extend-to-sentence':                   self.extendToSentence,
            'extend-to-word':                       self.extendToWord,
            'fill-paragraph':                       self.fillParagraph,
            'fill-region':                          self.fillRegion,
            'fill-region-as-paragraph':             self.fillRegionAsParagraph,
            'find-character':                       self.findCharacter,
            'find-character-extend-selection':      self.findCharacterExtendSelection,
            'find-word':                            self.findWord,
            'find-word-in-line':                    self.findWordInLine,
            'flush-lines':                          self.flushLines,
            'focus-to-body':                        self.focusToBody,
            'focus-to-log':                         self.focusToLog,
            'focus-to-minibuffer':                  self.focusToMinibuffer,
            'focus-to-tree':                        self.focusToTree,
            'forward-char':                         self.forwardCharacter,
            'forward-char-extend-selection':        self.forwardCharacterExtendSelection,
            'forward-page':                         self.forwardPage,
            'forward-page-extend-selection':        self.forwardPageExtendSelection,
            'forward-paragraph':                    self.forwardParagraph,
            'forward-paragraph-extend-selection':   self.forwardParagraphExtendSelection,
            'forward-sentence':                     self.forwardSentence,
            'forward-sentence-extend-selection':    self.forwardSentenceExtendSelection,
            'forward-end-word':                     self.forwardEndWord, # New in Leo 4.4.2.
            'forward-end-word-extend-selection':    self.forwardEndWordExtendSelection, # New in Leo 4.4.2.
            'forward-word':                         self.forwardWord,
            'forward-word-extend-selection':        self.forwardWordExtendSelection,
            'fully-expand-body-pane':               c.frame.fullyExpandBodyPane,
            'fully-expand-log-pane':                c.frame.fullyExpandLogPane,
            'fully-expand-pane':                    c.frame.fullyExpandPane,
            'fully-expand-outline-pane':            c.frame.fullyExpandOutlinePane,
            'goto-char':                            self.gotoCharacter,
            'goto-global-line':                     self.gotoGlobalLine,
            'goto-line':                            self.gotoLine,
            'hide-body-pane':                       c.frame.hideBodyPane,
            'hide-log-pane':                        c.frame.hideLogPane,
            'hide-pane':                            c.frame.hidePane,
            'hide-outline-pane':                    c.frame.hideOutlinePane,
            'how-many':                             self.howMany,
            # Use indentBody in leoCommands.py
            'indent-relative':                      self.indentRelative,
            'indent-rigidly':                       self.tabIndentRegion,
            'indent-to-comment-column':             self.indentToCommentColumn,
            'insert-icon':                          self.insertIcon,
            'insert-newline':                       self.insertNewline,
            'insert-parentheses':                   self.insertParentheses,
            'keep-lines':                           self.keepLines,
            'kill-paragraph':                       self.killParagraph,
            'line-number':                          self.lineNumber,
            'move-lines-down':                      self.moveLinesDown,
            'move-lines-up':                        self.moveLinesUp,
            'move-past-close':                      self.movePastClose,
            'move-past-close-extend-selection':     self.movePastCloseExtendSelection,
            'newline-and-indent':                   self.insertNewLineAndTab,
            'next-line':                            self.nextLine,
            'next-line-extend-selection':           self.nextLineExtendSelection,
            'previous-line':                        self.prevLine,
            'previous-line-extend-selection':       self.prevLineExtendSelection,
            'remove-blank-lines':                   self.removeBlankLines,
            'remove-space-from-lines':              self.removeSpaceFromLines,
            'remove-tab-from-lines':                self.removeTabFromLines,
            'reverse-region':                       self.reverseRegion,
            'reverse-sort-lines':                   self.reverseSortLines,
            'reverse-sort-lines-ignoring-case':     self.reverseSortLinesIgnoringCase,
            'scroll-down-half-page':                self.scrollDownHalfPage,                
            'scroll-down-line':                     self.scrollDownLine,
            'scroll-down-page':                     self.scrollDownPage,
            'scroll-outline-down-line':             self.scrollOutlineDownLine,
            'scroll-outline-down-page':             self.scrollOutlineDownPage,
            'scroll-outline-left':                  self.scrollOutlineLeft,
            'scroll-outline-right':                 self.scrollOutlineRight,
            'scroll-outline-up-line':               self.scrollOutlineUpLine,
            'scroll-outline-up-page':               self.scrollOutlineUpPage,
            'scroll-up-half-page':                  self.scrollUpHalfPage,                        
            'scroll-up-line':                       self.scrollUpLine,
            'scroll-up-page':                       self.scrollUpPage,
            'select-all':                           self.selectAllText,
            # Exists, but can not be executed via the minibuffer.
            # 'self-insert-command':                self.selfInsertCommand,
            'set-comment-column':                   self.setCommentColumn,
            'set-extend-mode':                      self.setExtendMode,
            'set-fill-column':                      self.setFillColumn,
            'set-fill-prefix':                      self.setFillPrefix,
            #'set-mark-command':                    self.setRegion,
            'show-colors':                          self.showColors,
            'show-fonts':                           self.showFonts,
            'simulate-begin-drag':                  self.simulateBeginDrag,
            'simulate-end-drag':                    self.simulateEndDrag,
            'sort-columns':                         self.sortColumns,
            'sort-fields':                          self.sortFields,
            'sort-lines':                           self.sortLines,
            'sort-lines-ignoring-case':             self.sortLinesIgnoringCase,
            'split-line':                           self.splitLine,
            'tabify':                               self.tabify,
            'toggle-extend-mode':                   self.toggleExtendMode,
            'transpose-chars':                      self.transposeCharacters,
            'transpose-lines':                      self.transposeLines,
            'transpose-words':                      self.transposeWords,
            'untabify':                             self.untabify,
            'upcase-region':                        self.upCaseRegion,
            'upcase-word':                          self.upCaseWord,
            'view-lossage':                         self.viewLossage,
            'what-line':                            self.whatLine,
        }
    #@-node:ekr.20050920084036.55: getPublicCommands (editCommandsClass)
    #@+node:ekr.20061012113455:doNothing
    def doNothing (self,event):

        '''A placeholder command, useful for testing bindings.'''

        # g.trace()
        pass
    #@nonl
    #@-node:ekr.20061012113455:doNothing
    #@-node:ekr.20050929155208: birth
    #@+node:ekr.20100209160132.5763:cache (leoEditCommands)
    def clearAllCaches (self,event=None):
        c = self.c
        if c.cacher:
            c.cacher.clearAllCaches()

    def clearCache (self,event=None):
        c = self.c
        if c.cacher:
            c.cacher.clearCache()
    #@-node:ekr.20100209160132.5763:cache (leoEditCommands)
    #@+node:ekr.20050920084036.57:capitalization & case
    #@+node:ekr.20051015114221:capitalizeWord & up/downCaseWord
    def capitalizeWord (self,event):
        '''Capitalize the word at the cursor.'''
        self.capitalizeHelper(event,'cap','capitalize-word')

    def downCaseWord (self,event):
        '''Convert all characters of the word at the cursor to lower case.'''
        self.capitalizeHelper(event,'low','downcase-word')

    def upCaseWord (self,event):
        '''Convert all characters of the word at the cursor to UPPER CASE.'''
        self.capitalizeHelper(event,'up','upcase-word')
    #@-node:ekr.20051015114221:capitalizeWord & up/downCaseWord
    #@+node:ekr.20050920084036.145:changePreviousWord (not used)
    def changePreviousWord (self,event):

        k = self.k ; stroke = k.stroke
        w = self.editWidget(event)
        if not w: return

        i = w.getInsertPoint()
        self.beginCommand(undoType='change-previous-word')
        self.moveWordHelper(event,extend=False,forward=False)

        if stroke == '<Alt-c>':
            self.capitalizeWord(event)
        elif stroke == '<Alt-u>':
            self.upCaseWord(event)
        elif stroke == '<Alt-l>':
            self.downCaseWord(event)

        w.setInsertPoint(i)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.145:changePreviousWord (not used)
    #@+node:ekr.20051015114221.1:capitalizeHelper
    def capitalizeHelper (self,event,which,undoType):

        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getWord(s,ins)
        word = s[i:j]
        # g.trace('word',repr(word))
        if not word.strip(): return

        self.beginCommand(undoType=undoType)

        if   which == 'cap':  word2 = word.capitalize()
        elif which == 'low':  word2 = word.lower()
        elif which == 'up':   word2 = word.upper()
        else: g.trace('can not happen: which = %s' %s (which))

        changed = word != word2
        # g.trace('changed',changed,'word2',repr(word2))

        if changed:
            w.delete(i,j)
            w.insert(i,word2)
            w.setSelectionRange(ins,ins,insert=ins)

        self.endCommand(changed=changed,setLabel=True)
    #@-node:ekr.20051015114221.1:capitalizeHelper
    #@-node:ekr.20050920084036.57:capitalization & case
    #@+node:ekr.20051022142249:clicks and focus (editCommandsClass)
    #@+node:ekr.20060211100905:activate-x-menu & activateMenu (editCommandsClass)
    def activateCmdsMenu    (self,event=None):
        '''Activate Leo's Cmnds menu.'''
        self.activateMenu('Cmds')

    def activateEditMenu    (self,event=None):
        '''Activate Leo's Edit menu.'''
        self.activateMenu('Edit')

    def activateFileMenu    (self,event=None):
        '''Activate Leo's File menu.'''
        self.activateMenu('File')

    def activateHelpMenu    (self,event=None):
        '''Activate Leo's Help menu.'''
        self.activateMenu('Help')

    def activateOutlineMenu (self,event=None):
        '''Activate Leo's Outline menu.'''
        self.activateMenu('Outline')

    def activatePluginsMenu (self,event=None):
        '''Activate Leo's Plugins menu.'''
        self.activateMenu('Plugins')

    def activateWindowMenu  (self,event=None):
        '''Activate Leo's Window menu.'''
        self.activateMenu('Window')

    def activateMenu (self,menuName):
        c = self.c
        c.frame.menu.activateMenu(menuName)
    #@-node:ekr.20060211100905:activate-x-menu & activateMenu (editCommandsClass)
    #@+node:ekr.20051022144825.1:cycleFocus
    def cycleFocus (self,event):

        '''Cycle the keyboard focus between Leo's outline, body and log panes.'''

        c = self.c ; k = c.k ; w = event.widget

        body = c.frame.body.bodyCtrl
        log  = c.frame.log.logCtrl
        tree = c.frame.tree.canvas

        # A hack for the Qt gui.
        if hasattr(w,'logCtrl'):
            w = w.logCtrl

        panes = [body,log,tree]

        # g.trace(w in panes,event.widget,panes)

        if w in panes:
            i = panes.index(w) + 1
            if i >= len(panes): i = 0
            pane = panes[i]
        else:
            pane = body

        # Warning: traces mess up the focus
        # g.pr(g.app.gui.widget_name(w),g.app.gui.widget_name(pane))

        # This works from the minibuffer *only* if there is no typing completion.
        c.widgetWantsFocusNow(pane)
        k.newMinibufferWidget = pane
        k.showStateAndMode()
    #@nonl
    #@-node:ekr.20051022144825.1:cycleFocus
    #@+node:ekr.20060613090701:cycleAllFocus
    editWidgetCount = 0
    logWidgetCount = 0

    def cycleAllFocus (self,event):

        '''Cycle the keyboard focus between Leo's outline,
        all body editors and all tabs in the log pane.'''

        c = self.c ; k = c.k
        w = event and event.widget # Does **not** require a text widget.

        pane = None ; w_name = g.app.gui.widget_name
        trace = False
        if trace:
            g.pr(
                '---- w',w_name(w),id(w),
                '#tabs',c.frame.log.numberOfVisibleTabs(),
                'bodyCtrl',w_name(c.frame.body.bodyCtrl),id(c.frame.body.bodyCtrl))

        # w may not be the present body widget, so test its name, not its id.
        if w_name(w).startswith('body'):
            n = c.frame.body.numberOfEditors
            # g.trace(self.editWidgetCount,n)
            if n > 1:
                self.editWidgetCount += 1
                if self.editWidgetCount == 1:
                    pane = c.frame.body.bodyCtrl
                elif self.editWidgetCount > n:
                    self.editWidgetCount = 0 ; self.logWidgetCount = 1
                    c.frame.log.selectTab('Log')
                    pane = c.frame.log.logCtrl
                else:
                    c.frame.body.cycleEditorFocus(event) ; pane = None
            else:
                self.editWidgetCount = 0 ; self.logWidgetCount = 1
                c.frame.log.selectTab('Log')
                pane = c.frame.log.logCtrl
        elif w_name(w).startswith('log'):
            n = c.frame.log.numberOfVisibleTabs()
            if n > 1:
                self.logWidgetCount += 1
                if self.logWidgetCount == 1:
                    c.frame.log.selectTab('Log')
                    pane = c.frame.log.logCtrl
                elif self.logWidgetCount > n:
                    self.logWidgetCount = 0
                    pane = c.frame.tree.canvas
                    # Use this to skip the tree pane.
                    #pane = c.frame.body.bodyCtrl
                else:
                    c.frame.log.cycleTabFocus()
                    pane = c.frame.log.logCtrl
            else:
                self.logWidgetCount = 0
                pane = c.frame.tree.canvas
                # Use this to skip the tree pane.
                # pane = c.frame.body.bodyCtrl
        else:
            pane = c.frame.body.bodyCtrl
            self.editWidgetCount = 1 ; self.logWidgetCount = 0

        if trace: g.pr('old: %10s new: %10s' % (w_name(w),w_name(pane)))

        if pane:
            k.newMinibufferWidget = pane
            c.widgetWantsFocusNow(pane)
            k.showStateAndMode()
    #@nonl
    #@-node:ekr.20060613090701:cycleAllFocus
    #@+node:ekr.20051022144825:focusTo...
    def focusToBody (self,event):
        '''Put the keyboard focus in Leo's body pane.'''
        c = self.c ; k = c.k
        c.bodyWantsFocusNow()
        if k:
            k.setDefaultInputState()
            k.showStateAndMode()

    def focusToLog (self,event):
        '''Put the keyboard focus in Leo's log pane.'''
        self.c.logWantsFocusNow()

    def focusToMinibuffer (self,event):
        '''Put the keyboard focus in Leo's minibuffer.'''
        self.c.minibufferWantsFocusNow()

    def focusToTree (self,event):
        '''Put the keyboard focus in Leo's outline pane.'''
        self.c.treeWantsFocusNow()
    #@-node:ekr.20051022144825:focusTo...
    #@+node:ekr.20060211063744.1:clicks in the headline
    # These call the actual event handlers so as to trigger hooks.

    def clickHeadline (self,event=None):
        '''Simulate a click in the headline of the presently selected node.'''
        c = self.c ; p = c.p
        c.frame.tree.onHeadlineClick(event,p=p)

    def doubleClickHeadline (self,event=None):
        '''Simulate a double click in headline of the presently selected node.'''
        return self.clickHeadline(event)

    def rightClickHeadline (self,event=None):
        '''Simulate a right click in the headline of the presently selected node.'''
        c = self.c ; p = c.p
        c.frame.tree.onHeadlineRightClick(event,p=p)
    #@-node:ekr.20060211063744.1:clicks in the headline
    #@+node:ekr.20060211055455:clicks in the icon box
    # These call the actual event handlers so as to trigger hooks.

    def clickIconBox (self,event=None):
        '''Simulate a click in the icon box of the presently selected node.'''
        c = self.c ; p = c.p
        c.frame.tree.onIconBoxClick(event,p=p)

    def doubleClickIconBox (self,event=None):
        '''Simulate a double-click in the icon box of the presently selected node.'''
        c = self.c ; p = c.p
        c.frame.tree.onIconBoxDoubleClick(event,p=p)

    def rightClickIconBox (self,event=None):

        '''Simulate a right click in the icon box of the presently selected node.'''
        c = self.c ; p = c.p
        c.frame.tree.onIconBoxRightClick(event,p=p)
    #@-node:ekr.20060211055455:clicks in the icon box
    #@+node:ekr.20060211062025:clickClickBox
    # Call the actual event handlers so as to trigger hooks.

    def clickClickBox (self,event=None):

        '''Simulate a click in the click box (+- box) of the presently selected node.'''

        c = self.c ; p = c.p
        c.frame.tree.onClickBoxClick(event,p=p)
    #@-node:ekr.20060211062025:clickClickBox
    #@+node:ekr.20060211063744.2:simulate...Drag
    # These call the drag setup methods which in turn trigger hooks.

    def simulateBeginDrag (self,event=None):

        '''Simulate the start of a drag in the presently selected node.'''
        c = self.c ; p = c.p
        c.frame.tree.startDrag(event,p=p)

    def simulateEndDrag (self,event=None):

        '''Simulate the end of a drag in the presently selected node.'''
        c = self.c

        # Note: this assumes that tree.startDrag has already been called.
        c.frame.tree.endDrag(event)
    #@-node:ekr.20060211063744.2:simulate...Drag
    #@-node:ekr.20051022142249:clicks and focus (editCommandsClass)
    #@+node:ekr.20051019183105:color & font
    #@+node:ekr.20051019183105.1:show-colors
    def showColors (self,event):

        '''Open a tab in the log pane showing various color pickers.'''

        c = self.c ; log = c.frame.log ; tabName = 'Colors'

        if log.frameDict.get(tabName):
            log.selectTab(tabName)
        else:
            log.selectTab(tabName)
            log.createColorPicker(tabName)
    #@-node:ekr.20051019183105.1:show-colors
    #@+node:ekr.20051019201809:editCommands.show-fonts & helpers
    def showFonts (self,event):

        '''Open a tab in the log pane showing a font picker.'''

        c = self.c ; log = c.frame.log ; tabName = 'Fonts'

        if log.frameDict.get(tabName):
            log.selectTab(tabName)
        else:
            log.selectTab(tabName)
            log.createFontPicker(tabName)
    #@-node:ekr.20051019201809:editCommands.show-fonts & helpers
    #@-node:ekr.20051019183105:color & font
    #@+node:ekr.20050920084036.132:comment column...
    #@+node:ekr.20050920084036.133:setCommentColumn
    def setCommentColumn (self,event):

        '''Set the comment column for the indent-to-comment-column command.'''

        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        ins = w.getInsertPoint()
        row,col = g.convertPythonIndexToRowCol(s,ins)
        self.ccolumn = col
    #@nonl
    #@-node:ekr.20050920084036.133:setCommentColumn
    #@+node:ekr.20050920084036.134:indentToCommentColumn
    def indentToCommentColumn (self,event):

        '''Insert whitespace to indent the line containing the insert point to the comment column.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType='indent-to-comment-column')

        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        line = s[i:j]
        c1 = int(self.ccolumn)
        line2 = ' ' * c1 + line.lstrip()
        if line2 != line:
            w.delete(i,j)
            w.insert(i,line2)
        w.setInsertPoint(i+c1)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.134:indentToCommentColumn
    #@-node:ekr.20050920084036.132:comment column...
    #@+node:ekr.20050920084036.62:esc methods for Python evaluation
    #@+node:ekr.20050920084036.63:watchEscape (Revise)
    def watchEscape (self,event):

        k = self.k

        if not k.inState():
            k.setState('escape','start',handler=self.watchEscape)
            k.setLabelBlue('Esc ')
        elif k.getStateKind() == 'escape':
            state = k.getState('escape')
            # hi1 = k.keysymHistory [0]
            # hi2 = k.keysymHistory [1]
            data1 = leoKeys.keyHandlerClass.lossage[0]
            data2 = leoKeys.keyHandlerClass.lossage[1]
            ch1, stroke1 = data1
            ch2, stroke2 = data2

            if state == 'esc esc' and event.keysym == ':':
                self.evalExpression(event)
            elif state == 'evaluate':
                self.escEvaluate(event)
            # elif hi1 == hi2 == 'Escape':
            elif stroke1 == 'Escape' and stroke2 == 'Escape':
                k.setState('escape','esc esc')
                k.setLabel('Esc Esc -')
            elif event.keysym not in ('Shift_L','Shift_R'):
                k.keyboardQuit(event)
    #@-node:ekr.20050920084036.63:watchEscape (Revise)
    #@+node:ekr.20050920084036.64:escEvaluate (Revise)
    def escEvaluate (self,event):

        k = self.k
        w = self.editWidget(event)
        if not w: return

        if k.getLabel() == 'Eval:':
            k.setLabel('')

        if event.keysym == 'Return':
            expression = k.getLabel()
            try:
                ok = False
                result = eval(expression,{},{})
                result = str(result)
                i = w.getInsertPoint()
                w.insert(i,result)
                ok = True
            finally:
                k.keyboardQuit(event)
                if not ok:
                    k.setLabel('Error: Invalid Expression')
        else:
            k.updateLabel(event)
    #@-node:ekr.20050920084036.64:escEvaluate (Revise)
    #@-node:ekr.20050920084036.62:esc methods for Python evaluation
    #@+node:ekr.20050920084036.65:evalExpression
    def evalExpression (self,event):

        '''Evaluate a Python Expression entered in the minibuffer.'''

        k = self.k ; state = k.getState('eval-expression')

        if state == 0:
            k.setLabelBlue('Eval: ',protect=True)
            k.getArg(event,'eval-expression',1,self.evalExpression)
        else:
            k.clearState()
            try:
                e = k.arg
                result = str(eval(e,{},{}))
                k.setLabelGrey('Eval: %s -> %s' % (e,result))
            except Exception:
                k.setLabelGrey('Invalid Expression: %s' % e)
    #@nonl
    #@-node:ekr.20050920084036.65:evalExpression
    #@+node:ekr.20050920084036.66:fill column and centering
    #@+at
    # These methods are currently just used in tandem to center the 
    # line or region within the fill column.
    # for example, dependent upon the fill column, this text:
    # 
    # cats
    # raaaaaaaaaaaats
    # mats
    # zaaaaaaaaap
    # 
    # may look like
    # 
    #                                  cats
    #                            raaaaaaaaaaaats
    #                                  mats
    #                              zaaaaaaaaap
    # 
    # after an center-region command via Alt-x.
    #@-at
    #@@c

    #@+others
    #@+node:ekr.20050920084036.67:centerLine
    def centerLine (self,event):

        '''Centers line within current fill column'''

        k = self.k ; w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        i,j = g.getLine(s,w.getInsertPoint())
        line = s [i:j].strip()
        if not line or len(line) >= self.fillColumn: return

        self.beginCommand(undoType='center-line')
        n = (self.fillColumn-len(line)) / 2
        ws = ' ' * n
        k = g.skip_ws(s,i)
        if k > i: w.delete(i,k-i)
        w.insert(i,ws)
        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.67:centerLine
    #@+node:ekr.20050920084036.68:setFillColumn
    def setFillColumn (self,event):

        '''Set the fill column used by the center-line and center-region commands.'''

        k = self.k ; state = k.getState('set-fill-column')

        if state == 0:
            k.setLabelBlue('Set Fill Column: ')
            k.getArg(event,'set-fill-column',1,self.setFillColumn)
        else:
            k.clearState()
            try:
                n = int(k.arg)
                k.setLabelGrey('fill column is: %d' % n)
                k.commandName = 'set-fill-column %d' % n
            except ValueError:
                k.resetLabel()
    #@-node:ekr.20050920084036.68:setFillColumn
    #@+node:ekr.20050920084036.69:centerRegion
    def centerRegion (self,event):

        '''Centers the selected text within the fill column'''

        k = self.k ; w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        sel_1, sel_2 = w.getSelectionRange()
        ind, junk = g.getLine(s,sel_1)
        junk, end = g.getLine(s,sel_2)

        self.beginCommand(undoType='center-region')

        inserted = 0
        while ind < end:
            s = w.getAllText()
            i, j = g.getLine(s,ind)
            line = s [i:j].strip()
            # g.trace(len(line),repr(line))
            if len(line) >= self.fillColumn:
                ind = j
            else:
                n = int((self.fillColumn-len(line))/2)
                inserted += n
                k = g.skip_ws(s,i)
                if k > i: w.delete(i,k-i)
                w.insert(i,' '*n)
                ind = j + n-(k-i)

        w.setSelectionRange(sel_1,sel_2+inserted)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.69:centerRegion
    #@+node:ekr.20050920084036.70:setFillPrefix
    def setFillPrefix( self, event ):

        '''Make the selected text the fill prefix.'''

        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        i,j = w.getSelectionRange()
        self.fillPrefix = s[i:j]
    #@-node:ekr.20050920084036.70:setFillPrefix
    #@+node:ekr.20050920084036.71:_addPrefix
    def _addPrefix (self,ntxt):

        ntxt = ntxt.split('.')
        ntxt = map(lambda a: self.fillPrefix+a,ntxt)
        ntxt = '.'.join(ntxt)
        return ntxt
    #@-node:ekr.20050920084036.71:_addPrefix
    #@-others
    #@-node:ekr.20050920084036.66:fill column and centering
    #@+node:ekr.20060417194232:find (quick)
    #@+node:ekr.20060925151926:backward/findCharacter & helper
    def backwardFindCharacter (self,event):
        return self.findCharacterHelper(event,backward=True,extend=False)

    def backwardFindCharacterExtendSelection (self,event):
        return self.findCharacterHelper(event,backward=True,extend=True)

    def findCharacter (self,event):
        return self.findCharacterHelper(event,backward=False,extend=False)

    def findCharacterExtendSelection (self,event):
        return self.findCharacterHelper(event,backward=False,extend=True)
    #@nonl
    #@+node:ekr.20060417194232.1:findCharacterHelper
    def findCharacterHelper (self,event,backward,extend):

        '''Put the cursor at the next occurance of a character on a line.'''

        c = self.c ; k = c.k ; tag = 'find-char' ; state = k.getState(tag)

        if state == 0:
            w = self.editWidget(event) # Sets self.w
            if not w: return
            self.event = event
            self.backward = backward
            self.extend = extend or self.extendMode # Bug fix: 2010/01/19
            self.insert = w.getInsertPoint()
            s = '%s character%s: ' % (
                g.choose(backward,'Backward find','Find'),
                g.choose(extend,' & extend',''))
            k.setLabelBlue(s,protect=True)
            # Get the arg without touching the focus.
            k.getArg(event,tag,1,self.findCharacter,oneCharacter=True,useMinibuffer=False)
        else:
            event = self.event ; w = self.w
            backward = self.backward
            extend = self.extend or self.extendMode
            ch = k.arg ; s = w.getAllText()
            ins = w.toPythonIndex(self.insert)
            i = ins + g.choose(backward,-1,+1) # skip the present character.
            if backward:
                start = 0
                j = s.rfind(ch,start,max(start,i)) # Skip the character at the cursor.
                if j > -1: self.moveToHelper(event,j,extend)
            else:
                end = len(s)
                j = s.find(ch,min(i,end),end) # Skip the character at the cursor.
                if j > -1: self.moveToHelper(event,j,extend)
            k.resetLabel()
            k.clearState()
    #@nonl
    #@-node:ekr.20060417194232.1:findCharacterHelper
    #@-node:ekr.20060925151926:backward/findCharacter & helper
    #@+node:ekr.20060417194232.2:findWord and FindWordOnLine & helper
    def findWord(self,event):

        '''Put the cursor at the next word that starts with a character.'''

        return self.findWordHelper(event,oneLine=False)

    def findWordInLine(self,event):

        '''Put the cursor at the next word (on a line) that starts with a character.'''

        return self.findWordHelper(event,oneLine=True)

    #@+node:ekr.20080408060320.1:findWordHelper
    def findWordHelper (self,event,oneLine):

        k = self.k ; tag = 'find-word' ; state = k.getState(tag)

        if state == 0:
            w = self.editWidget(event) # Sets self.w
            if not w: return
            self.oneLineFlag = oneLine
            k.setLabelBlue('Find word %sstarting with: ' % (
                g.choose(oneLine,'in line ','')))
            k.getArg(event,tag,1,self.findWord,oneCharacter=True)
        else:        
            ch = k.arg ; w = self.w ; c = k.c
            if ch:
                i = w.getInsertPoint()
                s = w.getAllText()
                if self.oneLineFlag:
                    end = s.find('\n',i) # Limit searches to this line.
                    if end == -1: end = len(s)
                else:
                    end = len(s)

                while i < end:
                    i = s.find(ch,i+1,end) # Ensure progress and i > 0.
                    if i == -1:
                        break
                    elif not g.isWordChar(s[i-1]):
                        w.setSelectionRange(i,i,insert=i)
                        break

            k.resetLabel()
            k.clearState()
    #@-node:ekr.20080408060320.1:findWordHelper
    #@-node:ekr.20060417194232.2:findWord and FindWordOnLine & helper
    #@-node:ekr.20060417194232:find (quick)
    #@+node:ekr.20050920084036.72:goto...
    #@+node:ekr.20050929115226:gotoCharacter
    def gotoCharacter (self,event):

        '''Put the cursor at the n'th character of the buffer.'''

        k = self.k ; state = k.getState('goto-char')

        if state == 0:
            w = self.editWidget(event) # Sets self.w
            if not w: return
            k.setLabelBlue("Goto n'th character: ")
            k.getArg(event,'goto-char',1,self.gotoCharacter)
        else:
            n = k.arg ; w = self.w ; ok = False
            if n.isdigit():
                n = int(n)
                if n >= 0:
                    w.setInsertPoint(n)
                    w.seeInsertPoint()
                    ok = True
            if not ok:
                g.es('goto-char takes non-negative integer argument',color='blue')
            k.resetLabel()
            k.clearState()
    #@-node:ekr.20050929115226:gotoCharacter
    #@+node:ekr.20060417181052:gotoGlobalLine
    def gotoGlobalLine (self,event):

        '''Put the cursor at the n'th line of a file or script.
        This is a minibuffer interface to Leo's legacy Go To Line number command.'''

        c = self.c
        k = self.k ; tag = 'goto-global-line' ; state = k.getState(tag)

        if state == 0:
            w = self.editWidget(event) # Sets self.w
            if not w: return
            k.setLabelBlue('Goto global line: ')
            k.getArg(event,tag,1,self.gotoGlobalLine)
        else:
            n = k.arg
            k.resetLabel()
            k.clearState()
            if n.isdigit():
                c.goToLineNumber(c).go(n=int(n))
    #@-node:ekr.20060417181052:gotoGlobalLine
    #@+node:ekr.20050929124234:gotoLine
    def gotoLine (self,event):

        '''Put the cursor at the n'th line of the buffer.'''

        k = self.k ; state = k.getState('goto-line')

        if state == 0:
            w = self.editWidget(event) # Sets self.w
            if not w: return
            k.setLabelBlue('Goto line: ')
            k.getArg(event,'goto-line',1,self.gotoLine)
        else:
            n = k.arg ;  w = self.w
            if n.isdigit():
                s = w.getAllText()
                i = g.convertRowColToPythonIndex(s,n,0)
                w.setInsertPoint(i)
                w.seeInsertPoint()
            k.resetLabel()
            k.clearState()
    #@-node:ekr.20050929124234:gotoLine
    #@-node:ekr.20050920084036.72:goto...
    #@+node:ekr.20071114081313:icons...
    #@+at
    # 
    # To do:
    # 
    # - Define standard icons in a subfolder of Icons folder?
    # - Tree control recomputes height of each line.
    #@-at
    #@+node:ekr.20080108092811: Helpers
    #@+node:ekr.20080108091349:appendImageDictToList
    def appendImageDictToList(self,aList,iconDir,path,xoffset,**kargs):

        trace = False and not g.unitTesting
        c = self.c
        path = c.os_path_finalize_join(iconDir,path)
        relPath = g.makePathRelativeTo(path,iconDir)

        image,image_height = g.app.gui.getTreeImage(c,path)
        if not image:
            g.es('can not load image:',path)
            return xoffset

        if image_height is None:
            yoffset = 0
        else:
            yoffset = 0 # (c.frame.tree.line_height-image_height)/2
            # TNB: I suspect this is being done again in the drawing code

        newEntry = {
            'type' : 'file',
            'file' : path,
            'relPath': relPath,
            'where' : 'beforeHeadline',
            'yoffset' : yoffset, 'xoffset' : xoffset, 'xpad' : 1, # -2,
            'on' : 'tnode',
        }
        newEntry.update(kargs)  # may switch 'on' to 'vnode'
        aList.append (newEntry)
        xoffset += 2

        return xoffset
    #@-node:ekr.20080108091349:appendImageDictToList
    #@+node:ekr.20090701125429.6013:dHash
    def dHash(self, d):
        """Hash a dictionary"""
        return ''.join(['%s%s' % (str(k),str(d[k])) for k in sorted(d)])
    #@-node:ekr.20090701125429.6013:dHash
    #@+node:tbrown.20080119085249:getIconList
    def getIconList(self, p):
        """Return list of icons for position p, call setIconList to apply changes"""

        trace = False and not g.unitTesting
        if trace:
            if p == self.c.rootPosition(): g.trace('='*40)
            g.trace(p.h)

        fromVnode = []
        if hasattr(p.v,'unknownAttributes'):
            if trace: g.trace(p.v.u)
            fromVnode = [dict(i) for i in p.v.u.get('icons',[])]
            for i in fromVnode: i['on'] = 'vnode'

        if trace and fromVnode: g.trace('fromVnode',fromVnode,p.h)

        return fromVnode
    #@-node:tbrown.20080119085249:getIconList
    #@+node:tbrown.20080119085249.1:setIconList & helpers
    def setIconList(self, p, l):
        """Set list of icons for position p to l"""

        trace = False and not g.unitTesting

        current = self.getIconList(p)
        if not l and not current: return  # nothing to do
        lHash = ''.join([self.dHash(i) for i in l])
        cHash = ''.join([self.dHash(i) for i in current])
        # if trace: g.trace('lHash:',lHash)
        # if trace: g.trace('cHash:',cHash)
        if lHash == cHash:
            # no difference between original and current list of dictionaries
            return

        if trace: g.trace(l)

        self._setIconListHelper(p, l, p.v)

    #@+node:ekr.20090701125429.6012:_setIconListHelper
    def _setIconListHelper(self, p, subl, uaLoc):
        """icon setting code common between v and t nodes

        p - postion
        subl - list of icons for the v or t node
        uaLoc - the v or t node"""

        trace = False and not g.unitTesting

        if subl: # Update the uA.
            if not hasattr(uaLoc,'unknownAttributes'):
                uaLoc.unknownAttributes = {}
            uaLoc.unknownAttributes['icons'] = list(subl)
            # g.es((p.h,uaLoc.unknownAttributes['icons']))
            uaLoc.unknownAttributes["lineYOffset"] = 3
            uaLoc._p_changed = 1
            p.setDirty()
            if trace: g.trace('uA',uaLoc.u,uaLoc)
        else: # delete the uA.
            if hasattr(uaLoc,'unknownAttributes'):
                if 'icons' in uaLoc.unknownAttributes:
                    del uaLoc.unknownAttributes['icons']
                    uaLoc.unknownAttributes["lineYOffset"] = 0
                    uaLoc._p_changed = 1
                    p.setDirty()
            if trace: g.trace('del uA[icons]',uaLoc)
    #@-node:ekr.20090701125429.6012:_setIconListHelper
    #@-node:tbrown.20080119085249.1:setIconList & helpers
    #@-node:ekr.20080108092811: Helpers
    #@+node:ekr.20071114082418:deleteFirstIcon
    def deleteFirstIcon (self,event=None):

        c = self.c ; p = c.p

        aList = self.getIconList(p)

        if aList:
            self.setIconList(p, aList[1:])
            c.setChanged(True)
            c.redraw_after_icons_changed()
    #@nonl
    #@-node:ekr.20071114082418:deleteFirstIcon
    #@+node:ekr.20071114092622:deleteIconByName
    def deleteIconByName (self,t,name,relPath): ### t not used.
        """for use by the right-click remove icon callback"""
        c = self.c ; p = c.p

        aList = self.getIconList(p)
        if not aList: return

        basePath = c.os_path_finalize_join(g.app.loadDir,"..","Icons")
        absRelPath = c.os_path_finalize_join(basePath,relPath)
        name = c.os_path_finalize(name)

        newList = []
        for d in aList:
            name2 = d.get('file')
            name2 = c.os_path_finalize(name2)
            name2rel = d.get('relPath')
            # g.trace('name',name,'\nrelPath',relPath,'\nabsRelPath',absRelPath,'\nname2',name2,'\nname2rel',name2rel)
            if not (name == name2 or absRelPath == name2 or relPath == name2rel):
                newList.append(d)

        if len(newList) != len(aList):
            self.setIconList(p, newList)       
            c.setChanged(True)
            c.redraw_after_icons_changed()
        else:
            g.trace('not found',name)
    #@nonl
    #@-node:ekr.20071114092622:deleteIconByName
    #@+node:ekr.20071114085054:deleteLastIcon
    def deleteLastIcon (self,event=None):

        c = self.c ; p = c.p

        aList = self.getIconList(p)

        if aList:
            self.setIconList(p, aList[:-1])
            c.setChanged(True)
            c.redraw_after_icons_changed()
    #@nonl
    #@-node:ekr.20071114085054:deleteLastIcon
    #@+node:ekr.20071114082418.1:deleteNodeIcons
    def deleteNodeIcons (self,event=None):

        c = self.c ; p = c.p

        if hasattr(p.v,"unknownAttributes"):
            a = p.v.unknownAttributes
            p.v._p_changed = 1
            self.setIconList(p,[])
            a["lineYOffset"] = 0
            p.setDirty()
            c.setChanged(True)
            c.redraw_after_icons_changed()
    #@-node:ekr.20071114082418.1:deleteNodeIcons
    #@+node:ekr.20071114081313.1:insertIcon
    def insertIcon (self,event=None):

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

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

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

        if not paths: return

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

        aList2 = self.getIconList(p)
        aList2.extend(aList)
        self.setIconList(p, aList2)
        c.setChanged(True)
        c.redraw_after_icons_changed()
    #@-node:ekr.20071114081313.1:insertIcon
    #@+node:ekr.20080108090719:insertIconFromFile
    def insertIconFromFile (self,path,p=None,pos=None,**kargs):

        trace = False and not g.unitTesting
        c = self.c
        if not p: p = c.p

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

        aList = [] ; xoffset = 2
        xoffset = self.appendImageDictToList(aList,iconDir,path,xoffset,**kargs)

        aList2 = self.getIconList(p)
        if pos is None: pos = len(aList2)
        aList2.insert(pos,aList[0])
        self.setIconList(p, aList2)
        c.setChanged(True)
        c.redraw_after_icons_changed()
        # c.redraw()
    #@-node:ekr.20080108090719:insertIconFromFile
    #@-node:ekr.20071114081313:icons...
    #@+node:ekr.20050920084036.74:indent...
    #@+node:ekr.20050920084036.76:deleteIndentation
    def deleteIndentation (self,event):

        '''Delete indentation in the presently line.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return


        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        line = s[i:j]
        line2 = s[i:j].lstrip()
        delta = len(line) - len(line2)
        if delta:
            self.beginCommand(undoType='delete-indentation')

            w.delete(i,j)
            w.insert(i,line2)
            ins -= delta
            w.setSelectionRange(ins,ins,insert=ins)

            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.76:deleteIndentation
    #@+node:ekr.20050920084036.78:indentRelative
    def indentRelative (self,event):

        '''The indent-relative command indents at the point based on the previous
        line (actually, the last non-empty line.) It inserts whitespace at the
        point, moving point, until it is underneath an indentation point in the
        previous line.

        An indentation point is the end of a sequence of whitespace or the end of
        the line. If the point is farther right than any indentation point in the
        previous line, the whitespace before point is deleted and the first
        indentation point then applicable is used. If no indentation point is
        applicable even then whitespace equivalent to a single tab is inserted.'''

        c = self.c ; undoType = 'indent-relative' ; w = self.editWidget(event)
        if not w: return
        s = w.getAllText()
        ins = w.getInsertPoint()
        oldSel = w.getSelectionRange()
        oldYview = w.getYScrollPosition()
        # Find the previous non-blank line
        i,j = g.getLine(s,ins)
        while 1:
            if i <= 0: return
            i,j = g.getLine(s,i-1)
            line = s[i:j]
            if line.strip(): break
        self.beginCommand(undoType=undoType)
        try:
            k = g.skip_ws(s,i)
            ws = s[i:k]
            i2,j2 = g.getLine(s,ins)
            k = g.skip_ws(s,i2)
            line = ws + s[k:j2]
            w.delete(i2,j2)
            w.insert(i2,line)
            w.setInsertPoint(i2+len(ws))
            c.frame.body.onBodyChanged(undoType,oldSel=oldSel,oldText=s,oldYview=oldYview)
        finally:
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.78:indentRelative
    #@-node:ekr.20050920084036.74:indent...
    #@+node:ekr.20050920084036.85:insert & delete...
    #@+node:ekr.20060417171125:addSpace/TabToLines & removeSpace/TabFromLines & helper
    def addSpaceToLines (self,event):
        '''Add a space to start of all lines, or all selected lines.'''
        self.addRemoveHelper(event,ch=' ',add=True,undoType='add-space-to-lines')

    def addTabToLines (self,event):
        '''Add a tab to start of all lines, or all selected lines.'''
        self.addRemoveHelper(event,ch='\t',add=True,undoType='add-tab-to-lines')

    def removeSpaceFromLines (self,event):
        '''Remove a space from start of all lines, or all selected lines.'''
        self.addRemoveHelper(event,ch=' ',add=False,undoType='remove-space-from-lines')

    def removeTabFromLines (self,event):
        '''Remove a tab from start of all lines, or all selected lines.'''
        self.addRemoveHelper(event,ch='\t',add=False,undoType='remove-tab-from-lines')
    #@+node:ekr.20060417172056:addRemoveHelper
    def addRemoveHelper(self,event,ch,add,undoType):

        c = self.c ; k = self.k ; w = self.editWidget(event)
        if not w: return

        if w.hasSelection():s = w.getSelectedText()
        else:               s = w.getAllText()
        if not s: return

        # Insert or delete spaces instead of tabs when negative tab width is in effect.
        d = c.scanAllDirectives() ; width = d.get('tabwidth')
        if ch == '\t' and width < 0: ch = ' ' * abs(width)

        self.beginCommand(undoType=undoType)

        lines = g.splitLines(s)

        if add:
            result = [ch + line for line in lines]
        else:
            result = [g.choose(line.startswith(ch),line[len(ch):],line) for line in lines]

        result = ''.join(result)

        # g.trace('add',add,'hasSelection',w.hasSelection(),'result',repr(result))

        if w.hasSelection():
            i,j = w.getSelectionRange()
            w.delete(i,j)
            w.insert(i,result)
            w.setSelectionRange(i,i+len(result))
        else:
            w.setAllText(result)
            w.setSelectionRange(0,len(s))

        self.endCommand(changed=True,setLabel=True)

    #@-node:ekr.20060417172056:addRemoveHelper
    #@-node:ekr.20060417171125:addSpace/TabToLines & removeSpace/TabFromLines & helper
    #@+node:ekr.20051026092433.1:backwardDeleteCharacter
    def backwardDeleteCharacter (self,event=None):

        '''Delete the character to the left of the cursor.'''

        c = self.c ; p = c.p
        w = self.editWidget(event)
        if not w: return

        wname = c.widget_name(w)
        ins = w.getInsertPoint()
        i,j = w.getSelectionRange()
        # g.trace(wname,i,j,ins)

        if wname.startswith('body'):
            self.beginCommand()
            try:
                d = c.scanAllDirectives(p)
                tab_width = d.get("tabwidth",c.tab_width)
                changed = True
                if i != j:
                    w.delete(i,j)
                    w.setSelectionRange(i,i,insert=i)
                elif i == 0:
                    changed = False
                elif tab_width > 0:
                    w.delete(ins-1)
                    w.setSelectionRange(ins-1,ins-1,insert=ins-1)
                else:
                    #@                << backspace with negative tab_width >>
                    #@+node:ekr.20051026092746:<< backspace with negative tab_width >>
                    s = prev = w.getAllText()
                    ins = w.getInsertPoint()
                    i,j = g.getLine(s,ins)
                    s = prev = s[i:ins]
                    n = len(prev)
                    abs_width = abs(tab_width)

                    # Delete up to this many spaces.
                    n2 = (n % abs_width) or abs_width
                    n2 = min(n,n2) ; count = 0

                    while n2 > 0:
                        n2 -= 1
                        ch = prev[n-count-1]
                        if ch != ' ': break
                        else: count += 1

                    # Make sure we actually delete something.
                    i = ins-(max(1,count))
                    w.delete(i,ins)
                    w.setSelectionRange(i,i,insert=i)
                    #@-node:ekr.20051026092746:<< backspace with negative tab_width >>
                    #@nl
            finally:
                self.endCommand(changed=True,setLabel=False) # Necessary to make text changes stick.
        else:
            # No undo in this widget.
            # Make sure we actually delete something if we can.
            s = w.getAllText()
            if i != j:
                j = max(i,min(j,len(s)))
                w.delete(i,j)
                w.setSelectionRange(i,i,insert=i)
            elif ins != 0:
                # Do nothing at the start of the headline.
                w.delete(ins-1)
                ins = ins-1
                w.setSelectionRange(ins,ins,insert=ins)
    #@-node:ekr.20051026092433.1:backwardDeleteCharacter
    #@+node:ekr.20070325094935:cleanAllLines
    def cleanAllLines (self,event):

        '''Clean all lines in the selected tree.'''

        c = self.c ; current = c.p
        w = c.frame.body.bodyCtrl
        if not w: return

        for p in current.self_and_subtree():
            c.selectPosition(p)
            w.setSelectionRange(0,0,insert=0)
            c.editCommands.cleanLines(event)
        c.selectPosition(current)
        c.redraw_after_icons_changed()
    #@-node:ekr.20070325094935:cleanAllLines
    #@+node:ekr.20060415112257:cleanLines
    def cleanLines (self,event):

        '''Removes leading whitespace from otherwise blanks lines.'''

        k = self.k ; w = self.editWidget(event)
        if not w: return

        if w.hasSelection():
            s = w.getSelectedText()
        else:
            s = w.getAllText()

        lines = [] ; changed = False
        for line in g.splitlines(s):
            if line.strip():
                lines.append(line)
            else:
                if line.endswith('\n'):
                    lines.append('\n')
                changed = changed or '\n' != line

        if changed:
            self.beginCommand(undoType='clean-lines')
            result = ''.join(lines)
            if w.hasSelection():
                i,j = w.getSelectionRange()
                w.delete(i,j)
                w.insert(i,result)
                w.setSelectionRange(i,j+len(result))
            else:
                w.delete(0,'end')
                w.insert(0,result)
            self.endCommand(changed=changed,setLabel=True)
    #@-node:ekr.20060415112257:cleanLines
    #@+node:ekr.20060414085834:clearSelectedText
    def clearSelectedText (self,event):

        '''Delete the selected text.'''

        c = self.c ; w = self.editWidget(event)
        if not w: return

        i,j = w.getSelectionRange()
        if i == j: return

        self.beginCommand(undoType='clear-selected-text')

        w.delete(i,j)
        w.setInsertPoint(i)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20060414085834:clearSelectedText
    #@+node:ekr.20050920084036.87:deleteNextChar
    def deleteNextChar (self,event):

        '''Delete the character to the right of the cursor.'''

        c = self.c ; w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        i,j = w.getSelectionRange()

        self.beginCommand(undoType='delete-char')

        changed = True
        if i != j:
            w.delete(i,j)
            w.setInsertPoint(i)
        elif j < len(s):
            w.delete(i)
            w.setInsertPoint(i)
        else:
            changed = False

        self.endCommand(changed=changed,setLabel=False)
    #@-node:ekr.20050920084036.87:deleteNextChar
    #@+node:ekr.20050920084036.135:deleteSpaces
    def deleteSpaces (self,event,insertspace=False):

        '''Delete all whitespace surrounding the cursor.'''

        c = self.c ; w = self.editWidget(event)
        undoType = g.choose(insertspace,'insert-space','delete-spaces')
        if not w: return
        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        w1 = ins-1
        while w1 >= i and s[w1].isspace():
            w1 -= 1
        w1 += 1
        w2 = ins
        while w2 <= j and s[w2].isspace():
            w2 += 1
        spaces = s[w1:w2]
        if spaces:
            self.beginCommand(undoType=undoType)
            if insertspace: s = s[:w1] + ' ' + s[w2:]
            else:           s = s[:w1] + s[w2:]
            w.setAllText(s)
            w.setInsertPoint(w1)
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.135:deleteSpaces
    #@+node:ekr.20050920084036.138:insertNewLine
    def insertNewLine (self,event):

        '''Insert a newline at the cursor.'''

        c = self.c ; k = c.k ; w = self.editWidget(event)
        if not w: return

        assert g.app.gui.isTextWidget(w)
        name = c.widget_name(w)
        if name.startswith('head'): return

        oldSel = w.getSelectionRange()
        # g.trace('oldSel',oldSel)

        self.beginCommand(undoType='newline')

        # New in Leo 4.5: use the same logic as in selfInsertCommand.
        self.insertNewlineHelper(w=w,oldSel=oldSel,undoType=None)
        k.setInputState('insert')
        k.showStateAndMode()

        self.endCommand()

    insertNewline = insertNewLine
    #@-node:ekr.20050920084036.138:insertNewLine
    #@+node:ekr.20050920084036.86:insertNewLineAndTab (changed)
    def insertNewLineAndTab (self,event):

        '''Insert a newline and tab at the cursor.'''

        c = self.c ; k = c.k
        w = self.editWidget(event) ; p = c.p
        if not w: return

        assert g.app.gui.isTextWidget(w)
        name = c.widget_name(w)
        if name.startswith('head'): return

        self.beginCommand(undoType='insert-newline-and-indent')

        # New in Leo 4.5: use the same logic as in selfInsertCommand.
        oldSel = w.getSelectionRange()
        self.insertNewlineHelper(w=w,oldSel=oldSel,undoType=None)
        self.updateTab(p,w,smartTab=False)
        k.setInputState('insert')
        k.showStateAndMode()

        self.endCommand(changed=True,setLabel=False)
    #@-node:ekr.20050920084036.86:insertNewLineAndTab (changed)
    #@+node:ekr.20050920084036.139:insertParentheses
    def insertParentheses (self,event):

        '''Insert () at the cursor.'''

        w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType='insert-parenthesis')

        i = w.getInsertPoint()
        w.insert(i,'()')
        w.setInsertPoint(i+1)

        self.endCommand(changed=True,setLabel=False)
    #@-node:ekr.20050920084036.139:insertParentheses
    #@+node:ekr.20050920084036.141:removeBlankLines
    def removeBlankLines (self,event):

        '''The remove-blank-lines command removes lines containing nothing but
        whitespace. If there is a text selection, only lines within the selected
        text are affected; otherwise all blank lines in the selected node are
        affected.'''

        c = self.c
        head,lines,tail,oldSel,oldYview = c.getBodyLines()

        changed = False ; result = []
        for line in lines:
            if line.strip():
                result.append(line)
            else:
                changed = True
        result = ''.join(result)

        if changed:
            oldSel = None ; undoType = 'remove-blank-lines'
            c.updateBodyPane(head,result,tail,undoType,oldSel,oldYview)
    #@-node:ekr.20050920084036.141:removeBlankLines
    #@+node:ekr.20051125080855:selfInsertCommand, helpers
    def selfInsertCommand(self,event,action='insert'):

        '''Insert a character in the body pane.
        This is the default binding for all keys in the body pane.'''

        trace = False and not g.unitTesting # or c.config.getBool('trace_masterCommand')
        verbose = True
        w = self.editWidget(event)
        if not w: return 'break'
        #@    << set local vars >>
        #@+node:ekr.20061103114242:<< set local vars >>
        c = self.c
        p = c.p
        gui = g.app.gui
        ch = gui.eventChar(event)
        keysym = gui.eventKeysym(event)
        # stroke = gui.eventStroke(event)
        if keysym == 'Return':
            ch = '\n' # This fixes the MacOS return bug.
        if keysym == 'Tab': # Support for wx_alt_gui plugin.
            ch = '\t'
        name = c.widget_name(w)
        oldSel =  name.startswith('body') and w.getSelectionRange() or (None,None)
        oldText = name.startswith('body') and p.b or ''
        undoType = 'Typing'
        brackets = self.openBracketsList + self.closeBracketsList
        inBrackets = ch and g.toUnicode(ch) in brackets
        # if trace: g.trace(name,repr(ch),ch and ch in brackets)
        #@nonl
        #@-node:ekr.20061103114242:<< set local vars >>
        #@nl
        if trace: g.trace('ch',repr(ch),'keysym',repr(keysym)) # ,'stroke',repr(stroke))
        if g.doHook("bodykey1",c=c,p=p,v=p,ch=ch,oldSel=oldSel,undoType=undoType):
            return "break" # The hook claims to have handled the event.
        if ch == '\t':
            self.updateTab(p,w)
        elif ch == '\b':
            # This is correct: we only come here if there no bindngs for this key. 
            self.backwardDeleteCharacter(event)
        elif ch in ('\r','\n'):
            ch = '\n'
            self.insertNewlineHelper(w,oldSel,undoType)
        elif inBrackets and self.autocompleteBrackets:
            self.updateAutomatchBracket(p,w,ch,oldSel)
        elif ch: # Null chars must not delete the selection.
            i,j = oldSel
            if i > j: i,j = j,i
            # Use raw insert/delete to retain the coloring.
            if i != j:                  w.delete(i,j)
            elif action == 'overwrite': w.delete(i)
            w.insert(i,ch)
            w.setInsertPoint(i+1)
            if inBrackets and self.flashMatchingBrackets:
                self.flashMatchingBracketsHelper(w,i,ch)               
        else:
            return 'break' # This method *always* returns 'break'

        # Set the column for up and down keys.
        spot = w.getInsertPoint()
        c.editCommands.setMoveCol(w,spot)

        # Update the text and handle undo.
        newText = w.getAllText()
        changed = newText != oldText
        if trace and verbose:
            g.trace('ch',repr(ch),'changed',changed,'newText',repr(newText[-10:]))
        if changed:
            # g.trace('ins',w.getInsertPoint())
            c.frame.body.onBodyChanged(undoType=undoType,
                oldSel=oldSel,oldText=oldText,oldYview=None)

        g.doHook("bodykey2",c=c,p=p,v=p,ch=ch,oldSel=oldSel,undoType=undoType)
        return 'break'
    #@+node:ekr.20090213065933.14:doPlainTab
    def doPlainTab(self,s,i,tab_width,w):

        '''Insert spaces equivalent to one tab.'''

        start,end = g.getLine(s,i)
        s2 = s[start:i]
        width = g.computeWidth(s2,tab_width)

        if tab_width > 0:
            w.insert(i,'\t')
            ins = i+1
        else:
            n = abs(tab_width) - (width % abs(tab_width))
            w.insert(i,' ' * n)
            ins = i+n

        w.setSelectionRange(ins,ins,insert=ins)
    #@-node:ekr.20090213065933.14:doPlainTab
    #@+node:ekr.20060627091557:flashCharacter
    def flashCharacter(self,w,i):

        bg      = self.bracketsFlashBg or 'DodgerBlue1'
        fg      = self.bracketsFlashFg or 'white'
        flashes = self.bracketsFlashCount or 3
        delay   = self.bracketsFlashDelay or 75

        w.flashCharacter(i,bg,fg,flashes,delay)
    #@-node:ekr.20060627091557:flashCharacter
    #@+node:ekr.20060627083506:flashMatchingBracketsHelper
    def flashMatchingBracketsHelper (self,w,i,ch):

        d = {}
        if ch in self.openBracketsList:
            for z in range(len(self.openBracketsList)):
                d [self.openBracketsList[z]] = self.closeBracketsList[z]
            reverse = False # Search forward
        else:
            for z in range(len(self.openBracketsList)):
                d [self.closeBracketsList[z]] = self.openBracketsList[z]
            reverse = True # Search backward

        delim2 = d.get(ch)

        s = w.getAllText()
        j = g.skip_matching_python_delims(s,i,ch,delim2,reverse=reverse)
        if j != -1:
            self.flashCharacter(w,j)
    #@-node:ekr.20060627083506:flashMatchingBracketsHelper
    #@+node:ekr.20060804095512:initBracketMatcher
    def initBracketMatcher (self,c):

        if len(self.openBracketsList) != len(self.closeBracketsList):

            g.es_print('bad open/close_flash_brackets setting: using defaults')
            self.openBracketsList  = '([{'
            self.closeBracketsList = ')]}'

        # g.trace('self.openBrackets',openBrackets)
        # g.trace('self.closeBrackets',closeBrackets)
    #@-node:ekr.20060804095512:initBracketMatcher
    #@+node:ekr.20051026171121:insertNewlineHelper
    def insertNewlineHelper (self,w,oldSel,undoType):

        trace = False and not g.unitTesting
        c = self.c ; p = c.p
        i,j = oldSel ; ch = '\n'
        if trace:
            s = w.widget.toPlainText()
            g.trace(i,j,len(s),w)

        if i != j:
            # No auto-indent if there is selected text.
            w.delete(i,j)
            w.insert(i,ch)
            w.setInsertPoint(i+1)
        else:
            w.insert(i,ch)
            w.setInsertPoint(i+1)

            if (c.autoindent_in_nocolor or 
                (c.frame.body.colorizer.useSyntaxColoring(p) and
                undoType != "Change")
            ):
                # No auto-indent if in @nocolor mode or after a Change command.
                self.updateAutoIndent(p,w)

        w.seeInsertPoint()
    #@-node:ekr.20051026171121:insertNewlineHelper
    #@+node:ekr.20051026171121.1:updateAutoIndent (leoEditCommands)
    def updateAutoIndent (self,p,w):

        c = self.c ; d = c.scanAllDirectives(p)
        tab_width = d.get("tabwidth",c.tab_width)
        # Get the previous line.
        s = w.getAllText()
        ins = w.getInsertPoint()
        i = g.skip_to_start_of_line(s,ins)
        i,j = g.getLine(s,i-1)
        s = s[i:j-1]
        # g.trace(i,j,repr(s))

        # Add the leading whitespace to the present line.
        junk, width = g.skip_leading_ws_with_indent(s,0,tab_width)
        # g.trace('width',width,'tab_width',tab_width)

        if s and s [-1] == ':':
            # For Python: increase auto-indent after colons.
            if g.findLanguageDirectives(c,p) == 'python':
                width += abs(tab_width)
        if self.smartAutoIndent:
            # Determine if prev line has unclosed parens/brackets/braces
            bracketWidths = [width] ; tabex = 0
            for i in range(0,len(s)):
                if s [i] == '\t':
                    tabex += tab_width-1
                if s [i] in '([{':
                    bracketWidths.append(i+tabex+1)
                elif s [i] in '}])' and len(bracketWidths) > 1:
                    bracketWidths.pop()
            width = bracketWidths.pop()
        ws = g.computeLeadingWhitespace(width,tab_width)
        if ws:
            i = w.getInsertPoint()
            w.insert(i,ws)
            w.setInsertPoint(i+len(ws))
    #@-node:ekr.20051026171121.1:updateAutoIndent (leoEditCommands)
    #@+node:ekr.20051027172949:updateAutomatchBracket
    def updateAutomatchBracket (self,p,w,ch,oldSel):

        # assert ch in ('(',')','[',']','{','}')

        c = self.c ; d = c.scanAllDirectives(p)
        i,j = oldSel
        language = d.get('language')
        s = w.getAllText()

        if ch in ('(','[','{',):
            automatch = language not in ('plain',)
            if automatch:
                ch = ch + {'(':')','[':']','{':'}'}.get(ch)
            if i != j: w.delete(i,j)
            w.insert(i,ch)
            if automatch:
                ins = w.getInsertPoint()
                w.setInsertPoint(ins-1)
        else:
            ins = w.getInsertPoint()
            ch2 = ins<len(s) and s[ins] or ''
            if ch2 in (')',']','}'):
                ins = w.getInsertPoint()
                w.setInsertPoint(ins+1)
            else:
                if i != j: w.delete(i,j)
                w.insert(i,ch)
                w.setInsertPoint(i+1)
    #@-node:ekr.20051027172949:updateAutomatchBracket
    #@+node:ekr.20051026092433:updateTab
    def updateTab (self,p,w,smartTab=True):

        c = self.c

        # g.trace('tab_width',tab_width)
        i,j = w.getSelectionRange()
            # Returns insert point if no selection, with i <= j.

        if i != j:
            # w.delete(i,j)
            c.indentBody()
        else:
            d = c.scanAllDirectives(p)
            tab_width = d.get("tabwidth",c.tab_width)
            # Get the preceeding characters.
            s = w.getAllText()
            # start = g.skip_to_start_of_line(s,i)
            start,end = g.getLine(s,i)
            before = s[start:i]
            after = s[i:end]
            if after.endswith('\n'): after = after[:-1]
            ws = g.get_leading_ws(before)
            s2 = s[start:i] # The characters before the insert point.

            # Only do smart tab at the start of a blank line.
            doSmartTab = (smartTab and c.smart_tab and i == start)
                # Truly at the start of the line.
                # and not after # Nothing *at all* after the cursor.
            # g.trace(doSmartTab,'i %s start %s after %s' % (i,start,repr(after)))

            if doSmartTab:
                self.updateAutoIndent(p,w)
                # Add a tab if otherwise nothing would happen.
                if s == w.getAllText():
                    self.doPlainTab(s,i,tab_width,w)
            else:
                self.doPlainTab(s,i,tab_width,w)
    #@-node:ekr.20051026092433:updateTab
    #@-node:ekr.20051125080855:selfInsertCommand, helpers
    #@-node:ekr.20050920084036.85:insert & delete...
    #@+node:ekr.20050920084036.79:info...
    #@+node:ekr.20050920084036.80:howMany
    def howMany (self,event):

        '''Print how many occurances of a regular expression are found
        in the body text of the presently selected node.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        state = k.getState('how-many')
        if state == 0:
            k.setLabelBlue('How many: ',protect = True)
            k.getArg(event,'how-many',1,self.howMany)
        else:
            k.clearState()
            s = w.getAllText()
            reg = re.compile(k.arg)
            i = reg.findall(s)
            k.setLabelGrey('%s occurances of %s' % (len(i),k.arg))
    #@-node:ekr.20050920084036.80:howMany
    #@+node:ekr.20050920084036.81:lineNumber
    def lineNumber (self,event):

        '''Print the line and column number and percentage of insert point.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        i = w.getInsertPoint()
        row,col = g.convertPythonIndexToRowCol(s,i)
        percent = int((i*100)/len(s))

        k.setLabelGrey(
            'char: %s row: %d col: %d pos: %d (%d%% of %d)' % (
                repr(s[i]),row,col,i,percent,len(s)))
    #@-node:ekr.20050920084036.81:lineNumber
    #@+node:ekr.20050920084036.83:viewLossage
    def viewLossage (self,event):

        '''Put the Emacs-lossage in the minibuffer label.'''

        k = self.k

        g.es('lossage...')
        aList = leoKeys.keyHandlerClass.lossage
        aList.reverse()
        for data in aList:
            ch,stroke = data
            d = {' ':'Space','\t':'Tab','\b':'Backspace','\n':'Newline','\r':'Return'}
            g.es('',stroke or d.get(ch) or ch or 'None')
    #@-node:ekr.20050920084036.83:viewLossage
    #@+node:ekr.20050920084036.84:whatLine
    def whatLine (self,event):

        '''Print the line number of the line containing the cursor.'''

        k = self.k ; w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        i = w.getInsertPoint()
        row,col = g.convertPythonIndexToRowCol(s,i)

        k.keyboardQuit(event)
        k.setLabel("Line %s" % row)
    #@-node:ekr.20050920084036.84:whatLine
    #@-node:ekr.20050920084036.79:info...
    #@+node:ekr.20050920084036.88:line...
    #@+node:ekr.20050920084036.90:flushLines
    def flushLines (self,event):

        '''Delete each line that contains a match for regexp, operating on the text after point.

        In Transient Mark mode, if the region is active, the command operates on the region instead.'''

        k = self.k ; state = k.getState('flush-lines')

        if state == 0:
            k.setLabelBlue('Flush lines regexp: ',protect=True)
            k.getArg(event,'flush-lines',1,self.flushLines)
        else:
            k.clearState()
            k.resetLabel()
            self.linesHelper(event,k.arg,'flush')
            k.commandName = 'flush-lines %s' % k.arg
    #@-node:ekr.20050920084036.90:flushLines
    #@+node:ekr.20051002095724:keepLines
    def keepLines (self,event):

        '''Delete each line that does not contain a match for regexp, operating on the text after point.

        In Transient Mark mode, if the region is active, the command operates on the region instead.'''

        k = self.k ; state = k.getState('keep-lines')

        if state == 0:
            k.setLabelBlue('Keep lines regexp: ',protect=True)
            k.getArg(event,'keep-lines',1,self.keepLines)
        else:
            k.clearState()
            k.resetLabel()
            self.linesHelper(event,k.arg,'keep')
            k.commandName = 'keep-lines %s' % k.arg
    #@-node:ekr.20051002095724:keepLines
    #@+node:ekr.20050920084036.92:linesHelper
    def linesHelper (self,event,pattern,which):

        k = self.k
        w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType=which+'-lines')
        if w.hasSelection():
            i,end = w.getSelectionRange()
        else:
            i = w.getInsertPoint()
            end = 'end'
        txt = w.get(i,end)
        tlines = txt.splitlines(True)
        if which == 'flush':    keeplines = list(tlines)
        else:                   keeplines = []

        try:
            regex = re.compile(pattern)
            for n, z in enumerate(tlines):
                f = regex.findall(z)
                if which == 'flush' and f:
                    keeplines [n] = None
                elif f:
                    keeplines.append(z)
        except Exception:
            return
        if which == 'flush':
            keeplines = [x for x in keeplines if x != None]
        w.delete(i,end)
        w.insert(i,''.join(keeplines))
        w.setInsertPoint(i)
        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.92:linesHelper
    #@+node:ekr.20050920084036.77:splitLine
    def splitLine (self,event):

        '''Split a line at the cursor position.'''

        w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType='split-line')

        s = w.getAllText()
        ins = w.getInsertPoint()
        w.setAllText(s[:ins] + '\n' + s[ins:])
        w.setInsertPoint(ins+1)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.77:splitLine
    #@-node:ekr.20050920084036.88:line...
    #@+node:ekr.20050929114218:move cursor... (leoEditCommands)
    #@+node:ekr.20051218170358: general helpers
    #@+node:ekr.20060113130510:extendHelper
    def extendHelper (self,w,extend,spot,upOrDown=False):
        '''Handle the details of extending the selection.
        This method is called for all cursor moves.

        extend: Clear the selection unless this is True.
        spot:   The *new* insert point.
        '''

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

        ins = w.getInsertPoint()
        i,j = w.getSelectionRange()
        if trace: g.trace(
            'extend',extend,'ins',ins,'sel=',i,j,
            'spot=',spot,'moveSpot',self.moveSpot)

        # Reset the move spot if needed.
        if self.moveSpot is None or p.v != self.moveSpotNode:
            # g.trace('no spot')
            self.setMoveCol(w,g.choose(extend,ins,spot)) # sets self.moveSpot.
        elif extend:
            if i == j or self.moveSpot not in (i,j):
                # g.trace('spot not in sel')
                self.setMoveCol(w,ins) # sets self.moveSpot.
        else:
            if upOrDown:
                s = w.getAllText()
                i2,j2 = g.getLine(s,spot)
                line = s[i2:j2]
                row,col = g.convertPythonIndexToRowCol(s,spot)
                if True: # was j2 < len(s)-1:
                    n = min(self.moveCol,max(0,len(line)-1))
                else:
                    n = min(self.moveCol,max(0,len(line))) # A tricky boundary.
                # g.trace('using moveCol',self.moveCol,'line',repr(line),'n',n)
                spot = g.convertRowColToPythonIndex(s,row,n)
            else:  # Plain move forward or back.
                # g.trace('plain forward/back move')
                self.setMoveCol(w,spot) # sets self.moveSpot.

        if extend:
            if spot < self.moveSpot:
                w.setSelectionRange(spot,self.moveSpot,insert=spot)
            else:
                w.setSelectionRange(self.moveSpot,spot,insert=spot)
        else:
            w.setSelectionRange(spot,spot,insert=spot)

        w.seeInsertPoint()
        c.frame.updateStatusLine()
    #@-node:ekr.20060113130510:extendHelper
    #@+node:ekr.20051218122116:moveToHelper
    def moveToHelper (self,event,spot,extend):

        '''Common helper method for commands the move the cursor
        in a way that can be described by a Tk Text expression.'''

        c = self.c ; k = c.k ; w = self.editWidget(event)
        if not w: return

        c.widgetWantsFocusNow(w)

        # Put the request in the proper range.
        if c.widget_name(w).startswith('mini'):
            i,j = k.getEditableTextRange()
            if   spot < i: spot = i
            elif spot > j: spot = j

        self.extendHelper(w,extend,spot,upOrDown=False)
    #@-node:ekr.20051218122116:moveToHelper
    #@+node:ekr.20060209095101:setMoveCol
    def setMoveCol (self,w,spot):

        '''Set the column to which an up or down arrow will attempt to move.'''

        c = self.c ; p = c.p

        i,row,col = w.toPythonIndexRowCol(spot)

        self.moveSpot = i
        self.moveCol = col
        self.moveSpotNode = p.v

        # g.trace('moveSpot',i)
    #@nonl
    #@-node:ekr.20060209095101:setMoveCol
    #@-node:ekr.20051218170358: general helpers
    #@+node:ekr.20081123102100.1:backToHome
    def backToHome (self,event):

        '''Smart home:
        Position the point at the first non-blank character on the line,
        or the start of the line if already there.'''

        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        ins = w.getInsertPoint()
        if ins == 0 or not(s): return

        # Toggle back and forth between start of line and first-non-blank character.
        i,j = g.getLine(s,ins)
        i1 = i
        while i < j and s[i] in (' \t'):
            i += 1
        if i == ins:
            i = i1

        self.moveToHelper(event,i,extend=False)
    #@-node:ekr.20081123102100.1:backToHome
    #@+node:ekr.20050920084036.75:backToIndentation
    def backToIndentation (self,event):

        '''Position the point at the first non-blank character on the line.'''

        w = self.editWidget(event)
        if not w: return

        # None of the other cursor move commands are undoable.
        # self.beginCommand(undoType='back-to-indentation')

        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        while i < j and s[i] in (' \t'):
            i += 1

        self.moveToHelper(event,i,extend=False)

        # self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.75:backToIndentation
    #@+node:ekr.20051218141237:between lines & helper
    def nextLine (self,event):
        '''Move the cursor down, extending the selection if in extend mode.'''
        self.moveUpOrDownHelper(event,'down',extend=False)

    def nextLineExtendSelection (self,event):
        '''Extend the selection by moving the cursor down.'''
        self.moveUpOrDownHelper(event,'down',extend=True)

    def prevLine (self,event):
        '''Move the cursor up, extending the selection if in extend mode.'''
        self.moveUpOrDownHelper(event,'up',extend=False)

    def prevLineExtendSelection (self,event):
        '''Extend the selection by moving the cursor up.'''
        self.moveUpOrDownHelper(event,'up',extend=True)
    #@+node:ekr.20060113105246.1:moveUpOrDownHelper
    def moveUpOrDownHelper (self,event,direction,extend):

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

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

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

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

            self.extendHelper(w,extend,spot,upOrDown=True)
    #@-node:ekr.20060113105246.1:moveUpOrDownHelper
    #@-node:ekr.20051218141237:between lines & helper
    #@+node:ekr.20050920084036.148:buffers & helper
    def beginningOfBuffer (self,event):
        '''Move the cursor to the start of the body text.'''
        self.moveToBufferHelper(event,'home',extend=False)

    def beginningOfBufferExtendSelection (self,event):
        '''Extend the text selection by moving the cursor to the start of the body text.'''
        self.moveToBufferHelper(event,'home',extend=True)

    def endOfBuffer (self,event):
        '''Move the cursor to the end of the body text.'''
        self.moveToBufferHelper(event,'end',extend=False)

    def endOfBufferExtendSelection (self,event):
        '''Extend the text selection by moving the cursor to the end of the body text.'''
        self.moveToBufferHelper(event,'end',extend=True)
    #@+node:ekr.20100109094541.6227:moveToBufferHelper
    def moveToBufferHelper (self,event,spot,extend):

        c = self.c ; w = self.editWidget(event)
        if not w: return

        if hasattr(w,'leoMoveCursorHelper'):
            extend = extend or self.extendMode
            w.leoMoveCursorHelper(kind=spot,extend=extend)
            w.seeInsertPoint()
            c.frame.updateStatusLine()
        else:
            if spot == 'home':
                self.moveToHelper(event,0,extend=extend)
            elif spot == 'end':
                s = w.getAllText()
                self.moveToHelper(event,len(s),extend=extend)
            else:
                g.trace('can not happen: bad spot',spot)
    #@-node:ekr.20100109094541.6227:moveToBufferHelper
    #@-node:ekr.20050920084036.148:buffers & helper
    #@+node:ekr.20051213080533:characters & helper
    def backCharacter (self,event):
        '''Move the cursor back one character, extending the selection if in extend mode.'''
        self.moveToCharacterHelper(event,'left',extend=False)

    def backCharacterExtendSelection (self,event):
        '''Extend the selection by moving the cursor back one character.'''
        self.moveToCharacterHelper(event,'left',extend=True)

    def forwardCharacter (self,event):
        '''Move the cursor forward one character, extending the selection if in extend mode.'''
        self.moveToCharacterHelper(event,'right',extend=False)

    def forwardCharacterExtendSelection (self,event):
        '''Extend the selection by moving the cursor forward one character.'''
        self.moveToCharacterHelper(event,'right',extend=True)
    #@+node:ekr.20100109094541.6228:moveToCharacterHelper
    def moveToCharacterHelper (self,event,spot,extend):

        c = self.c ; w = self.editWidget(event)
        if not w: return

        if hasattr(w,'leoMoveCursorHelper'):
            extend = extend or self.extendMode
            w.leoMoveCursorHelper(kind=spot,extend=extend)
            w.seeInsertPoint()
            c.frame.updateStatusLine()
        else:
            i = w.getInsertPoint()
            if spot == 'left':
                i=max(0,i-1)
                self.moveToHelper(event,i,extend=extend)
            elif spot == 'right':
                i = min(i+1,len(w.getAllText()))
                self.moveToHelper(event,i,extend=extend)
            else:
                g.trace('can not happen: bad spot: %s' % spot)
    #@-node:ekr.20100109094541.6228:moveToCharacterHelper
    #@-node:ekr.20051213080533:characters & helper
    #@+node:ekr.20051218174113:clear/set/ToggleExtendMode
    def clearExtendMode (self,event):
        '''Turn off extend mode: cursor movement commands do not extend the selection.'''
        self.extendModeHelper(event,False)

    def setExtendMode (self,event):
        '''Turn on extend mode: cursor movement commands do extend the selection.'''
        self.extendModeHelper(event,True)

    def toggleExtendMode (self,event):
        '''Toggle extend mode, i.e., toggle whether cursor movement commands extend the selections.'''
        self.extendModeHelper(event,not self.extendMode)

    def extendModeHelper (self,event,val):

        c = self.c
        w = self.editWidget(event)
        if not w: return

        self.extendMode = val
        if not g.unitTesting:
            g.es('extend mode',g.choose(val,'on','off'),color='red')
        c.widgetWantsFocusNow(w)
    #@-node:ekr.20051218174113:clear/set/ToggleExtendMode
    #@+node:ekr.20050920084036.136:exchangePointMark
    def exchangePointMark (self,event):

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

        c = self.c
        w = self.editWidget(event)
        if not w: return

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

            ins = w.getInsertPoint()
            ins = g.choose(ins==i,j,i)
            w.setInsertPoint(ins)
            w.setSelectionRange(i,j,insert=None)
    #@-node:ekr.20050920084036.136:exchangePointMark
    #@+node:ekr.20061007082956:extend-to-line
    def extendToLine (self,event):

        '''Select the line at the cursor.'''

        c = self.c ; w = self.editWidget(event)
        if not w: return

        s = w.getAllText() ; n = len(s)
        i = w.getInsertPoint()

        while 0 <= i < n and not s[i] == '\n':
            i -= 1
        i += 1 ; i1 = i
        while 0 <= i < n and not s[i] == '\n':
            i += 1

        w.setSelectionRange(i1,i)
    #@-node:ekr.20061007082956:extend-to-line
    #@+node:ekr.20061007214835.4:extend-to-sentence
    def extendToSentence (self,event):

        '''Select the line at the cursor.'''

        c = self.c
        w = self.editWidget(event)
        if not w: return

        s = w.getAllText() ; n = len(s)
        i = w.getInsertPoint()

        i2 = 1 + s.find('.',i)
        if i2 == -1: i2 = n
        i1 = 1 + s.rfind('.',0,i2-1)

        w.setSelectionRange(i1,i2)
    #@nonl
    #@-node:ekr.20061007214835.4:extend-to-sentence
    #@+node:ekr.20060116074839.2:extend-to-word
    def extendToWord (self,event,direction='forward'):

        '''Select the word at the cursor.'''

        c = self.c
        w = self.editWidget(event)
        if not w: return

        s = w.getAllText() ; n = len(s)
        i = w.getInsertPoint()

        if direction == 'forward':
            while i < n and not g.isWordChar(s[i]):
                i += 1
        else:
            while 0 <= i < n and not g.isWordChar(s[i]):
                i -= 1

        while 0 <= i < n and g.isWordChar(s[i]):
            i -= 1
        i += 1
        i1 = i

        # Move to the end of the word.
        while 0 <= i < n and g.isWordChar(s[i]):
            i += 1

        w.setSelectionRange(i1,i)
    #@nonl
    #@-node:ekr.20060116074839.2:extend-to-word
    #@+node:ekr.20050920084036.140:movePastClose & helper
    def movePastClose (self,event):
        '''Move the cursor past the closing parenthesis.'''
        self.movePastCloseHelper(event,extend=False)

    def movePastCloseExtendSelection (self,event):
        '''Extend the selection by moving the cursor past the closing parenthesis.'''
        self.movePastCloseHelper(event,extend=True)
    #@+node:ekr.20051218171457:movePastCloseHelper
    def movePastCloseHelper (self,event,extend):

        c = self.c ; w = self.editWidget(event)
        if not w: return

        c.widgetWantsFocusNow(w)
        s = w.getAllText()
        ins = w.getInsertPoint()
        # Scan backwards for i,j.
        i = ins
        while i >= 0 and s[i] != '\n':
            if s[i] == '(': break
            i -= 1
        else: return
        j = ins
        while j >= 0 and s[j] != '\n':
            if s[j] == '(': break
            j -= 1
        if i < j: return
        # Scan forward for i2,j2.
        i2 = ins
        while i2 < len(s) and s[i2] != '\n':
            if s[i2] == ')': break
            i2 += 1
        else: return
        j2 = ins
        while j2 < len(s) and s[j2] != '\n':
            if s[j2] == ')': break
            j2 += 1
        if i2 > j2: return

        self.moveToHelper(event,i2+1,extend)
    #@-node:ekr.20051218171457:movePastCloseHelper
    #@-node:ekr.20050920084036.140:movePastClose & helper
    #@+node:ekr.20100109094541.6231:moveWithinLineHelper
    def moveWithinLineHelper (self,event,spot,extend):

        c = self.c ; w = self.editWidget(event)
        if not w: return

        # g.trace(hasattr(w,'leoMoveCursorHelper'))

        if hasattr(w,'leoMoveCursorHelper'):
            extend = extend or self.extendMode
            w.leoMoveCursorHelper(kind=spot,extend=extend)
            w.seeInsertPoint()
            c.frame.updateStatusLine()
        else:
            s = w.getAllText()
            ins = w.getInsertPoint()
            i,j = g.getLine(s,ins)
            if spot == 'start-line':
                self.moveToHelper(event,i,extend=extend)
            elif spot == 'end-line':
                # if g.match(s,i-1,'\n'): i -= 1
                self.moveToHelper(event,j,extend=extend)
            else:
                g.trace('can not happen: bad spot: %s' % spot)
    #@-node:ekr.20100109094541.6231:moveWithinLineHelper
    #@+node:ekr.20090530181848.6034:pages & helper
    def backPage (self,event):
        '''Move the cursor back one page,
        extending the selection if in extend mode.'''
        self.movePageHelper(event,kind='back',extend=False)

    def backPageExtendSelection (self,event):
        '''Extend the selection by moving the cursor back one page.'''
        self.movePageHelper(event,kind='back',extend=True)

    def forwardPage (self,event):
        '''Move the cursor forward one page,
        extending the selection if in extend mode.'''
        self.movePageHelper(event,kind='forward',extend=False)

    def forwardPageExtendSelection (self,event):
        '''Extend the selection by moving the cursor forward one page.'''
        self.movePageHelper(event,kind='forward',extend=True)
    #@+node:ekr.20090530181848.6035:movePageHelper
    def movePageHelper(self,event,kind,extend): # kind in back/forward.

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

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

        linesPerPage = 15 # To do.

        if hasattr(w,'leoMoveCursorHelper'):
            extend = extend or self.extendMode
            w.leoMoveCursorHelper(
                kind=g.choose(kind=='forward','page-down','page-up'),
                extend=extend,linesPerPage=linesPerPage)
            w.seeInsertPoint()
            c.frame.updateStatusLine()
        else:
            ins = w.getInsertPoint()
            s = w.getAllText()
            lines = g.splitLines(s)
            row,col = g.convertPythonIndexToRowCol(s,ins)
            row2 = g.choose(kind=='back',
                max(0,row-linesPerPage),
                min(row+linesPerPage,len(lines)-1))
            if row == row2: return
            spot = g.convertRowColToPythonIndex(s,row2,col,lines=lines)
            if trace: g.trace('spot',spot,'row2',row2)
            self.extendHelper(w,extend,spot,upOrDown=True)
    #@-node:ekr.20090530181848.6035:movePageHelper
    #@-node:ekr.20090530181848.6034:pages & helper
    #@+node:ekr.20050920084036.102:paragraphs & helpers
    def backwardParagraph (self,event):
        '''Move the cursor to the previous paragraph.'''
        self.backwardParagraphHelper (event,extend=False)

    def backwardParagraphExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the previous paragraph.'''
        self.backwardParagraphHelper (event,extend=True)

    def forwardParagraph (self,event):
        '''Move the cursor to the next paragraph.'''
        self.forwardParagraphHelper(event,extend=False)

    def forwardParagraphExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the next paragraph.'''
        self.forwardParagraphHelper(event,extend=True)
    #@+node:ekr.20051218133207:backwardParagraphHelper
    def backwardParagraphHelper (self,event,extend):

        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        i,j = w.getSelectionRange()
        # A hack for wx gui: set the insertion point to the end of the selection range.
        if g.app.unitTesting:
            w.setInsertPoint(j)
        i,j = g.getLine(s,j)
        line = s[i:j]

        if line.strip():
            # Find the start of the present paragraph.
            while i > 0:
                i,j = g.getLine(s,i-1)
                line = s[i:j]
                if not line.strip(): break

        # Find the end of the previous paragraph.
        while i > 0:
            i,j = g.getLine(s,i-1)
            line = s[i:j]
            if line.strip():
                i = j-1 ; break

        self.moveToHelper(event,i,extend)
    #@nonl
    #@-node:ekr.20051218133207:backwardParagraphHelper
    #@+node:ekr.20051218133207.1:forwardParagraphHelper
    def forwardParagraphHelper (self,event,extend):

        w = self.editWidget(event)
        if not w: return
        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        line = s[i:j]

        if line.strip(): # Skip past the present paragraph.
            self.selectParagraphHelper(w,i)
            i,j = w.getSelectionRange()
            j += 1

        # Skip to the next non-blank line.
        i = j
        while j < len(s):
            i,j = g.getLine(s,j)
            line = s[i:j]
            if line.strip(): break

        w.setInsertPoint(ins) # Restore the original insert point.
        self.moveToHelper(event,i,extend)
    #@-node:ekr.20051218133207.1:forwardParagraphHelper
    #@-node:ekr.20050920084036.102:paragraphs & helpers
    #@+node:ekr.20050920084036.131:sentences & helpers
    def backSentence (self,event):
        '''Move the cursor to the previous sentence.'''
        self.backSentenceHelper(event,extend=False)

    def backSentenceExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the previous sentence.'''
        self.backSentenceHelper(event,extend=True)

    def forwardSentence (self,event):
        '''Move the cursor to the next sentence.'''
        self.forwardSentenceHelper(event,extend=False)

    def forwardSentenceExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the next sentence.'''
        self.forwardSentenceHelper(event,extend=True)
    #@+node:ekr.20051213094517:backSentenceHelper
    def backSentenceHelper (self,event,extend):

        c = self.c
        w = self.editWidget(event)
        if not w: return

        c.widgetWantsFocusNow(w)
        s = w.getAllText()
        i = w.getInsertPoint()

        while i >= 0:
            if s[i] == '.': break
            i -= 1
        else: return

        j = i-1
        while j >= 0:
            if s[j] == '.':
                j += 1 ; break
            j -= 1
        else: j = 0

        while j < i and s[j].isspace():
            j += 1

        if j < i:
            self.moveToHelper(event,j,extend)
    #@-node:ekr.20051213094517:backSentenceHelper
    #@+node:ekr.20050920084036.137:forwardSentenceHelper
    def forwardSentenceHelper (self,event,extend):

        c = self.c
        w = self.editWidget(event)
        if not w: return

        c.widgetWantsFocusNow(w)

        s = w.getAllText()
        ins = w.getInsertPoint()
        i = s.find('.',ins) + 1
        i = min(i,len(s))
        self.moveToHelper(event,i,extend)
    #@-node:ekr.20050920084036.137:forwardSentenceHelper
    #@-node:ekr.20050920084036.131:sentences & helpers
    #@+node:ekr.20100109094541.6232:within lines
    def beginningOfLine (self,event):
        '''Move the cursor to the start of the line, extending the selection if in extend mode.'''
        self.moveWithinLineHelper(event,'start-line',extend=False)

    def beginningOfLineExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the start of the line.'''
        self.moveWithinLineHelper(event,'start-line',extend=True)

    def endOfLine (self,event):
        '''Move the cursor to the end of the line, extending the selection if in extend mode.'''
        self.moveWithinLineHelper(event,'end-line',extend=False)

    def endOfLineExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the end of the line.'''
        self.moveWithinLineHelper(event,'end-line',extend=True)
    #@-node:ekr.20100109094541.6232:within lines
    #@+node:ekr.20050920084036.149:words & helper
    def backwardWord (self,event):
        '''Move the cursor to the previous word.'''
        self.moveWordHelper(event,extend=False,forward=False)

    def backwardWordExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the next word.'''
        self.moveWordHelper(event,extend=True,forward=False)

    def forwardEndWord (self,event): # New in Leo 4.4.2
        '''Move the cursor to the next word.'''
        self.moveWordHelper(event,extend=False,forward=True,end=True)

    def forwardEndWordExtendSelection (self,event): # New in Leo 4.4.2
        '''Extend the selection by moving the cursor to the previous word.'''
        self.moveWordHelper(event,extend=True,forward=True,end=True)

    def forwardWord (self,event):
        '''Move the cursor to the next word.'''
        self.moveWordHelper(event,extend=False,forward=True)

    def forwardWordExtendSelection (self,event):
        '''Extend the selection by moving the cursor to the previous word.'''
        self.moveWordHelper(event,extend=True,forward=True)
    #@+node:ekr.20051218121447:moveWordHelper
    def moveWordHelper (self,event,extend,forward,end=False):

        '''Move the cursor to the next/previous word.
        The cursor is placed at the start of the word unless end=True'''

        c = self.c
        w = self.editWidget(event)
        if not w: return

        c.widgetWantsFocusNow(w)
        s = w.getAllText() ; n = len(s)
        i = w.getInsertPoint()

        if forward:
            # Unlike backward-word moves, there are two options...
            if end:
                while 0 <= i < n and not g.isWordChar(s[i]):
                    i += 1
                while 0 <= i < n and g.isWordChar(s[i]):
                    i += 1
            else:
                while 0 <= i < n and g.isWordChar(s[i]):
                    i += 1
                while 0 <= i < n and not g.isWordChar(s[i]):
                    i += 1
        else:
            i -= 1
            while 0 <= i < n and not g.isWordChar(s[i]):
                i -= 1
            while 0 <= i < n and g.isWordChar(s[i]):
                i -= 1
            i += 1

        self.moveToHelper(event,i,extend)
    #@nonl
    #@-node:ekr.20051218121447:moveWordHelper
    #@-node:ekr.20050920084036.149:words & helper
    #@-node:ekr.20050929114218:move cursor... (leoEditCommands)
    #@+node:ekr.20050920084036.95:paragraph...
    #@+others
    #@+node:ekr.20050920084036.99:backwardKillParagraph
    def backwardKillParagraph (self,event):

        '''Kill the previous paragraph.'''

        k = self.k ; c = k.c ; w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType='backward-kill-paragraph')
        try:
            self.backwardParagraphHelper(event,extend=True)
            i,j = w.getSelectionRange()
            if i > 0: i = min(i+1,j)
            c.killBufferCommands.kill(event,i,j,undoType=None)
            w.setSelectionRange(i,i,insert=i)
        finally:
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.99:backwardKillParagraph
    #@+node:ekr.20050920084036.100:fillRegion
    def fillRegion (self,event):

        '''Fill all paragraphs in the selected text.'''

        # New in Leo 4.4.4: just use reformat-paragraph logic.

        c = self.c ; p = c.p ; undoType = 'fill-region'
        w = self.editWidget(event)
        i,j = w.getSelectionRange()
        c.undoer.beforeChangeGroup(p,undoType)
        while 1:
            self.c.reformatParagraph(event,undoType='reformat-paragraph')
            ins = w.getInsertPoint()
            s = w.getAllText()
            if ins >= j or ins >= len(s):
                break
        c.undoer.afterChangeGroup(p,undoType)
    #@-node:ekr.20050920084036.100:fillRegion
    #@+node:ekr.20050920084036.104:fillRegionAsParagraph
    def fillRegionAsParagraph (self,event):

        '''Fill the selected text.'''

        k = self.k
        w = self.editWidget(event)
        if not w or not self._chckSel(event): return

        self.beginCommand(undoType='fill-region-as-paragraph')

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.104:fillRegionAsParagraph
    #@+node:ekr.20050920084036.103:fillParagraph
    def fillParagraph( self, event ):

        '''Fill the selected paragraph'''

        w = self.editWidget(event)
        if not w: return

        # Clear the selection range.
        i,j = w.getSelectionRange()
        w.setSelectionRange(i,i,insert=i)

        self.c.reformatParagraph(event)
    #@-node:ekr.20050920084036.103:fillParagraph
    #@+node:ekr.20050920084036.98:killParagraph
    def killParagraph (self,event):

        '''Kill the present paragraph.'''

        k = self.k ; c = k.c ; w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType='kill-paragraph')
        try:
            self.extendToParagraph(event)
            i,j = w.getSelectionRange()
            c.killBufferCommands.kill(event,i,j,undoType=None)
            w.setSelectionRange(i,i,insert=i)
        finally:
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.98:killParagraph
    #@+node:ekr.20050920084036.96:extend-to-paragraph & helper
    def extendToParagraph (self,event):

        '''Select the paragraph surrounding the cursor.'''

        w = self.editWidget(event)
        if not w: return
        s = w.getAllText() ; ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        line = s[i:j]

        # Find the start of the paragraph.
        if line.strip(): # Search backward.
            while i > 0:
                i2,j2 = g.getLine(s,i-1)
                line = s[i2:j2]
                if line.strip(): i = i2
                else: break # Use the previous line.
        else: # Search forward.
            while j < len(s):
                i,j = g.getLine(s,j)
                line = s[i:j]
                if line.strip(): break
            else: return

        # Select from i to the end of the paragraph.
        self.selectParagraphHelper(w,i)
    #@+node:ekr.20050920084036.97:selectParagraphHelper
    def selectParagraphHelper (self,w,start):

        '''Select from start to the end of the paragraph.'''

        s = w.getAllText()
        i1,j = g.getLine(s,start)
        while j < len(s):
            i,j2 = g.getLine(s,j)
            line = s[i:j2]
            if line.strip(): j = j2
            else: break

        j = max(start,j-1)
        w.setSelectionRange(i1,j,insert=j)
    #@-node:ekr.20050920084036.97:selectParagraphHelper
    #@-node:ekr.20050920084036.96:extend-to-paragraph & helper
    #@-others
    #@-node:ekr.20050920084036.95:paragraph...
    #@+node:ekr.20050920084036.105:region...
    #@+others
    #@+node:ekr.20050920084036.108:tabIndentRegion (indent-rigidly)
    def tabIndentRegion (self,event):

        '''Insert a hard tab at the start of each line of the selected text.'''

        k = self.k
        w = self.editWidget(event)
        if not w or not self._chckSel(event): return

        self.beginCommand(undoType='indent-rigidly')

        s = w.getAllText()
        i1,j1 = w.getSelectionRange()
        i,junk = g.getLine(s,i1)
        junk,j = g.getLine(s,j1)

        lines = g.splitlines(s[i:j])
        n = len(lines)
        lines = g.joinLines(['\t' + line for line in lines])
        s = s[:i] + lines + s[j:]
        w.setAllText(s)

        # Retain original row/col selection.
        w.setSelectionRange(i1,j1+n,insert=j1+n)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.108:tabIndentRegion (indent-rigidly)
    #@+node:ekr.20050920084036.109:countRegion
    def countRegion (self,event):

        '''Print the number of lines and characters in the selected text.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        txt = w.getSelectedText()
        lines = 1 ; chars = 0
        for z in txt:
            if z == '\n': lines += 1
            else:         chars += 1

        k.setLabelGrey('Region has %s lines, %s character%s' % (
            lines,chars,g.choose(chars==1,'','s')))
    #@-node:ekr.20050920084036.109:countRegion
    #@+node:ekr.20060417183606:moveLinesDown
    def moveLinesDown (self,event):

        '''Move all lines containing any selected text down one line,
        moving to the next node if the lines are the last lines of the body.'''

        c = self.c ; w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        sel_1,sel_2 = w.getSelectionRange()
        i,junk = g.getLine(s,sel_1)
        i2,j   = g.getLine(s,sel_2)
        lines  = s[i:j]
        # Select from start of the first line to the *start* of the last line.
        # This prevents selection creep.
        n = i2-i 
        # g.trace('lines',repr(lines))

        self.beginCommand(undoType='move-lines-down')
        changed = False
        try:
            if j < len(s):
                next_i,next_j = g.getLine(s,j+1)
                next_line = s[next_i:next_j]
                n2 = next_j-next_i
                w.delete(i,next_j)
                w.insert(i,next_line+lines)
                w.setSelectionRange(i+n2,i+n2+n,insert=i+n2+n)
                changed = True
            elif g.app.gui.widget_name(w).startswith('body'):
                p = c.p
                if not p.hasThreadNext(): return
                w.delete(i,j)
                c.setBodyString(p,w.getAllText())
                p = p.threadNext()
                c.redraw(p)

                s = w.getAllText()
                w.insert(0,lines)
                if not lines.endswith('\n'): w.insert(len(lines),'\n')
                s = w.getAllText()
                w.setSelectionRange(0,n,insert=n)
                changed = True
        finally:
            self.endCommand(changed=changed,setLabel=True)
    #@-node:ekr.20060417183606:moveLinesDown
    #@+node:ekr.20060417183606.1:moveLinesUp
    def moveLinesUp (self,event):

        '''Move all lines containing any selected text up one line,
        moving to the previous node as needed.'''

        c = self.c ; w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        sel_1,sel_2 = w.getSelectionRange()
        i,junk = g.getLine(s,sel_1)
        i2,j   = g.getLine(s,sel_2)
        lines  = s[i:j]
        # Select from start of the first line to the *start* of the last line.
        # This prevents selection creep.
        n = i2-i 
        # g.trace('lines',repr(lines))

        self.beginCommand(undoType='move-lines-up')
        changed = False
        try:
            if i > 0:
                prev_i,prev_j = g.getLine(s,i-1)
                prev_line = s[prev_i:prev_j]
                w.delete(prev_i,j)
                w.insert(prev_i,lines+prev_line)
                w.setSelectionRange(prev_i,prev_i+n,insert=prev_i+n)
                changed = True
            elif g.app.gui.widget_name(w).startswith('body'):
                p = c.p
                if not p.hasThreadBack(): return
                w.delete(i,j)
                c.setBodyString(p,w.getAllText())
                p = p.threadBack()

                c.redraw(p)

                s = w.getAllText()
                if not s.endswith('\n'):
                    i = w.getInsertPoint()
                    w.insert(i,'\n')
                w.insert('end',lines)
                s = w.getAllText()
                ins = len(s)-len(lines)+n
                w.setSelectionRange(len(s)-len(lines),ins,insert=ins)
                changed = True
        finally:
            self.endCommand(changed=changed,setLabel=True)
    #@-node:ekr.20060417183606.1:moveLinesUp
    #@+node:ekr.20050920084036.110:reverseRegion
    def reverseRegion (self,event):

        '''Reverse the order of lines in the selected text.'''

        k = self.k
        w = self.editWidget(event)
        if not w or not self._chckSel(event): return

        self.beginCommand(undoType='reverse-region')

        s = w.getAllText()
        i1,j1 = w.getSelectionRange()
        i,junk = g.getLine(s,i1)
        junk,j = g.getLine(s,j1)

        txt = s[i:j]
        aList = txt.split('\n')
        aList.reverse()
        txt = '\n'.join(aList) + '\n'

        w.setAllText(s[:i1] + txt + s[j1:])
        ins = i1 + len(txt) - 1
        w.setSelectionRange(ins,ins,insert=ins)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.110:reverseRegion
    #@+node:ekr.20050920084036.111:up/downCaseRegion & helper
    def downCaseRegion (self,event):
        '''Convert all characters in the selected text to lower case.'''
        self.caseHelper(event,'low','downcase-region')

    def upCaseRegion (self,event):
        '''Convert all characters in the selected text to UPPER CASE.'''
        self.caseHelper(event,'up','upcase-region')

    def caseHelper (self,event,way,undoType):

        w = self.editWidget(event)
        if not w or not w.hasSelection(): return

        self.beginCommand(undoType=undoType)

        s = w.getAllText()
        i,j = w.getSelectionRange()
        ins = w.getInsertPoint()
        sel = g.choose(way=='low',s[i:j].lower(),s[i:j].upper())
        s2 = s[:i] + sel + s[j:]
        # g.trace('sel',repr(sel),'s2',repr(s2))
        changed = s2 != s
        if changed:
            w.setAllText(s2)
            w.setSelectionRange(i,j,insert=ins)

        self.endCommand(changed=changed,setLabel=True)
    #@-node:ekr.20050920084036.111:up/downCaseRegion & helper
    #@-others
    #@-node:ekr.20050920084036.105:region...
    #@+node:ekr.20060309060654:scrolling...
    #@+node:ekr.20050920084036.147:measure
    def measure (self,w):

        if hasattr(w,'linesPerPage'):
            # Preferred.  Qt implements this.
            n = w.linesPerPage()
            return max(2,n-3)
        else:
            s = w.getAllText()
            ins = w.getInsertPoint()
            start, junk = g.convertPythonIndexToRowCol(s,ins)
            start += 1 ; delta = 0
            ustart = start - 1
            while ustart >= 1 and w.indexIsVisible('%s.0' % ustart):
                delta += 1 ; ustart -= 1

            ustart = start + 1
            while w.indexIsVisible('%s.0' % ustart):
                delta += 1 ; ustart += 1

            return delta
    #@-node:ekr.20050920084036.147:measure
    #@+node:ekr.20050920084036.116:scrollUp/Down & helper
    def scrollDownHalfPage (self,event):
        '''Scroll the presently selected pane down one lline.'''
        self.scrollHelper(event,'down','half-page')

    def scrollDownLine (self,event):
        '''Scroll the presently selected pane down one lline.'''
        self.scrollHelper(event,'down','line')

    def scrollDownPage (self,event):
        '''Scroll the presently selected pane down one page.'''
        self.scrollHelper(event,'down','page')

    def scrollUpHalfPage (self,event):
        '''Scroll the presently selected pane down one lline.'''
        self.scrollHelper(event,'up','half-page')

    def scrollUpLine (self,event):
        '''Scroll the presently selected pane up one page.'''
        self.scrollHelper(event,'up','line')

    def scrollUpPage (self,event):
        '''Scroll the presently selected pane up one page.'''
        self.scrollHelper(event,'up','page')
    #@+node:ekr.20060113082917:scrollHelper
    def scrollHelper (self,event,direction,distance):

        '''Scroll the present pane up or down one page
        kind is in ('up/down-half-page/line/page)'''

        k = self.k ; c = k.c ; gui = g.app.gui
        w = gui.eventWidget(event)

        if not w: return

        if hasattr(w,'scrollDelegate'):
            kind = direction + '-' + distance
            w.scrollDelegate(kind)
        else:
            self.tkScrollHelper(event,direction,distance)

    def tkScrollHelper (self,event,direction,distance,extend=None):
        #Scroll body pane up/down (direction) by page/half-page/line (distance)
        #Note: Currently moves cursor, scrolls if needed to keep cursor visible
        k = self.k ; c = k.c ; gui = g.app.gui
        w = gui.eventWidget(event)
        if not w: return #  This does **not** require a text widget.

        if gui.isTextWidget(w):
            c.widgetWantsFocusNow(w)
            # Remember the original insert point.  This may become the moveSpot.
            ins1 = w.getInsertPoint()
            s = w.getAllText()
            row,col = g.convertPythonIndexToRowCol(s,ins1)
            # Compute the spot.
            # assume scroll by "page"
            delta = self.measure(w)
            if distance == 'half-page':
                delta = delta / 2
            elif distance == 'line':
                delta = 1
            row1 = g.choose(direction=='down',row+delta,row-delta)
            row1 = max(0,row1)
            spot = g.convertRowColToPythonIndex(s,row1,col)
            # g.trace('spot',spot,'row1',row1)
            self.extendHelper(w,extend,spot)
            w.seeInsertPoint()
    #@-node:ekr.20060113082917:scrollHelper
    #@-node:ekr.20050920084036.116:scrollUp/Down & helper
    #@+node:ekr.20060309060654.1:scrollOutlineUp/Down/Line/Page
    def scrollOutlineDownLine (self,event=None):
        '''Scroll the outline pane down one line.'''
        c = self.c ; tree = c.frame.tree
        if hasattr(tree,'scrollDelegate'):
            tree.scrollDelegate('down-line')
        elif hasattr(tree.canvas,'leo_treeBar'):
            a,b = tree.canvas.leo_treeBar.get()
            if b < 1.0: tree.canvas.yview_scroll(1,"unit")

    def scrollOutlineDownPage (self,event=None):
        '''Scroll the outline pane down one page.'''
        c = self.c ; tree = c.frame.tree
        if hasattr(tree,'scrollDelegate'):
            tree.scrollDelegate('down-page')
        elif hasattr(tree.canvas,'leo_treeBar'):
            a,b = tree.canvas.leo_treeBar.get()
            if b < 1.0: tree.canvas.yview_scroll(1,"page")

    def scrollOutlineUpLine (self,event=None):
        '''Scroll the outline pane up one line.'''
        c = self.c ; tree = c.frame.tree
        if hasattr(tree,'scrollDelegate'):
            tree.scrollDelegate('up-line')
        elif hasattr(tree.canvas,'leo_treeBar'):
            a,b = tree.canvas.leo_treeBar.get()
            if a > 0.0: tree.canvas.yview_scroll(-1,"unit")

    def scrollOutlineUpPage (self,event=None):
        '''Scroll the outline pane up one page.'''
        c = self.c ; tree = c.frame.tree
        if hasattr(tree,'scrollDelegate'):
            tree.scrollDelegate('up-page')
        elif hasattr(tree.canvas,'leo_treeBar'):
            a,b = tree.canvas.leo_treeBar.get()
            if a > 0.0: tree.canvas.yview_scroll(-1,"page")
    #@-node:ekr.20060309060654.1:scrollOutlineUp/Down/Line/Page
    #@+node:ekr.20060726154531:scrollOutlineLeftRight
    def scrollOutlineLeft (self,event=None):
        '''Scroll the outline left.'''
        c = self.c ; tree = c.frame.tree
        if hasattr(tree,'scrollDelegate'):
            tree.scrollDelegate('left')
        elif hasattr(tree.canvas,'xview_scroll'):
            tree.canvas.xview_scroll(1,"unit")

    def scrollOutlineRight (self,event=None):
        '''Scroll the outline left.'''
        c = self.c ; tree = c.frame.tree
        if hasattr(tree,'scrollDelegate'):
            tree.scrollDelegate('right')
        elif hasattr(tree.canvas,'xview_scroll'):
            tree.canvas.xview_scroll(-1,"unit")
    #@-node:ekr.20060726154531:scrollOutlineLeftRight
    #@-node:ekr.20060309060654:scrolling...
    #@+node:ekr.20050920084036.117:sort...
    #@@nocolor
    #@@color
    #@+at
    # XEmacs provides several commands for sorting text in a 
    # buffer.  All
    # operate on the contents of the region (the text between point 
    # and the
    # mark).  They divide the text of the region into many "sort 
    # records",
    # identify a "sort key" for each record, and then reorder the 
    # records
    # using the order determined by the sort keys.  The records are 
    # ordered so
    # that their keys are in alphabetical order, or, for numerical 
    # sorting, in
    # numerical order.  In alphabetical sorting, all upper-case 
    # letters `A'
    # through `Z' come before lower-case `a', in accordance with the 
    # ASCII
    # character sequence.
    # 
    #    The sort commands differ in how they divide the text into 
    # sort
    # records and in which part of each record they use as the sort 
    # key.
    # Most of the commands make each line a separate sort record, 
    # but some
    # commands use paragraphs or pages as sort records.  Most of the 
    # sort
    # commands use each entire sort record as its own sort key, but 
    # some use
    # only a portion of the record as the sort key.
    # 
    # `M-x sort-lines'
    #      Divide the region into lines and sort by comparing the 
    # entire text
    #      of a line.  A prefix argument means sort in descending 
    # order.
    # 
    # `M-x sort-paragraphs'
    #      Divide the region into paragraphs and sort by comparing 
    # the entire
    #      text of a paragraph (except for leading blank lines).  A 
    # prefix
    #      argument means sort in descending order.
    # 
    # `M-x sort-pages'
    #      Divide the region into pages and sort by comparing the 
    # entire text
    #      of a page (except for leading blank lines).  A prefix 
    # argument
    #      means sort in descending order.
    # 
    # `M-x sort-fields'
    #      Divide the region into lines and sort by comparing the 
    # contents of
    #      one field in each line.  Fields are defined as separated 
    # by
    #      whitespace, so the first run of consecutive 
    # non-whitespace
    #      characters in a line constitutes field 1, the second such 
    # run
    #      constitutes field 2, etc.
    # 
    #      You specify which field to sort by with a numeric 
    # argument: 1 to
    #      sort by field 1, etc.  A negative argument means sort in 
    # descending
    #      order.  Thus, minus 2 means sort by field 2 in 
    # reverse-alphabetical
    #      order.
    # 
    # `M-x sort-numeric-fields'
    #      Like `M-x sort-fields', except the specified field is 
    # converted to
    #      a number for each line and the numbers are compared.  
    # `10' comes
    #      before `2' when considered as text, but after it when 
    # considered
    #      as a number.
    # 
    # `M-x sort-columns'
    #      Like `M-x sort-fields', except that the text within each 
    # line used
    #      for comparison comes from a fixed range of columns.  An 
    # explanation
    #      is given below.
    # 
    #    For example, if the buffer contains:
    # 
    #      On systems where clash detection (locking of files being 
    # edited) is
    #      implemented, XEmacs also checks the first time you modify 
    # a buffer
    #      whether the file has changed on disk since it was last 
    # visited or
    #      saved.  If it has, you are asked to confirm that you want 
    # to change
    #      the buffer.
    # 
    # then if you apply `M-x sort-lines' to the entire buffer you 
    # get:
    # 
    #      On systems where clash detection (locking of files being 
    # edited) is
    #      implemented, XEmacs also checks the first time you modify 
    # a buffer
    #      saved.  If it has, you are asked to confirm that you want 
    # to change
    #      the buffer.
    #      whether the file has changed on disk since it was last 
    # visited or
    # 
    # where the upper case `O' comes before all lower case letters.  
    # If you
    # apply instead `C-u 2 M-x sort-fields' you get:
    # 
    #      saved.  If it has, you are asked to confirm that you want 
    # to change
    #      implemented, XEmacs also checks the first time you modify 
    # a buffer
    #      the buffer.
    #      On systems where clash detection (locking of files being 
    # edited) is
    #      whether the file has changed on disk since it was last 
    # visited or
    # 
    # where the sort keys were `If', `XEmacs', `buffer', `systems', 
    # and `the'.
    # 
    #    `M-x sort-columns' requires more explanation.  You specify 
    # the
    # columns by putting point at one of the columns and the mark at 
    # the other
    # column.  Because this means you cannot put point or the mark 
    # at the
    # beginning of the first line to sort, this command uses an 
    # unusual
    # definition of `region': all of the line point is in is 
    # considered part
    # of the region, and so is all of the line the mark is in.
    # 
    #    For example, to sort a table by information found in 
    # columns 10 to
    # 15, you could put the mark on column 10 in the first line of 
    # the table,
    # and point on column 15 in the last line of the table, and then 
    # use this
    # command.  Or you could put the mark on column 15 in the first 
    # line and
    # point on column 10 in the last line.
    # 
    #    This can be thought of as sorting the rectangle specified 
    # by point
    # and the mark, except that the text on each line to the left or 
    # right of
    # the rectangle moves along with the text inside the rectangle.  
    # *Note
    # Rectangles::.
    # 
    #@-at
    #@+node:ekr.20050920084036.118:sortLines commands
    def reverseSortLinesIgnoringCase(self,event):
        return self.sortLines(event,ignoreCase=True,reverse=True)

    def reverseSortLines(self,event):
        return self.sortLines(event,reverse=True)

    def sortLinesIgnoringCase(self,event):
        return self.sortLines(event,ignoreCase=True)

    def sortLines (self,event,ignoreCase=False,reverse=False):

        '''Sort lines of the selected text by comparing the entire text of a line.'''

        c = self.c ; k = c.k ; w = self.editWidget(event)
        if not self._chckSel(event): return

        undoType = g.choose(reverse,'reverse-sort-lines','sort-lines')
        self.beginCommand(undoType=undoType)
        try:
            s = w.getAllText()
            sel_1,sel_2 = w.getSelectionRange()
            ins = w.getInsertPoint()
            i,junk = g.getLine(s,sel_1)
            junk,j = g.getLine(s,sel_2)
            s2 = s[i:j]
            if not s2.endswith('\n'): s2 = s2+'\n'
            aList = g.splitLines(s2)
            def lower(s):
                if ignoreCase: return s.lower()
                else: return s
            aList.sort(key=lower)
                # key is a function that extracts args.
            if reverse:
                aList.reverse()
            s = g.joinLines(aList)
            w.delete(i,j)
            w.insert(i,s)
            w.setSelectionRange(sel_1,sel_2,insert=ins)
        finally:
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.118:sortLines commands
    #@+node:ekr.20050920084036.119:sortColumns
    def sortColumns (self,event):

        '''Sort lines of selected text using only lines in the given columns to do the comparison.'''

        k = self.k
        w = self.editWidget(event)
        if not self._chckSel(event): return
        self.beginCommand(undoType='sort-columns')
        try:
            s = w.getAllText()
            ins = w.getInsertPoint()
            sel_1,sel_2 = w.getSelectionRange()
            sint1,sint2 = g.convertPythonIndexToRowCol(s,sel_1)
            sint3,sint4 = g.convertPythonIndexToRowCol(s,sel_2)
            sint1 += 1 ; sint3 += 1
            i,junk = g.getLine(s,sel_1)
            junk,j = g.getLine(s,sel_2)
            txt = s[i:j]
            columns = [w.get('%s.%s' % (z,sint2),'%s.%s' % (z,sint4))
                for z in range(sint1,sint3+1)]
            aList = g.splitLines(txt)
            zlist = list(zip(columns,aList))
            zlist.sort()
            s = g.joinLines([z[1] for z in zlist])
            w.delete(i,j)
            w.insert(i,s)
            w.setSelectionRange(sel_1,sel_1+len(s),insert=sel_1+len(s))
        finally:
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.119:sortColumns
    #@+node:ekr.20050920084036.120:sortFields
    def sortFields (self,event,which=None):

        '''Divide the selected text into lines and sort by comparing the contents of
         one field in each line. Fields are defined as separated by whitespace, so
         the first run of consecutive non-whitespace characters in a line
         constitutes field 1, the second such run constitutes field 2, etc.

         You specify which field to sort by with a numeric argument: 1 to sort by
         field 1, etc. A negative argument means sort in descending order. Thus,
         minus 2 means sort by field 2 in reverse-alphabetical order.'''

        k = self.k
        w = self.editWidget(event)
        if not w or not self._chckSel(event): return

        self.beginCommand(undoType='sort-fields')

        s = w.getAllText()
        ins = w.getInsertPoint()
        r1,r2,r3,r4 = self.getRectanglePoints(w)
        i,junk = g.getLine(s,r1)
        junk,j = g.getLine(s,r4)
        txt = s[i:j] # bug reported by pychecker.
        txt = txt.split('\n')
        fields = []
        fn = r'\w+'
        frx = re.compile(fn)
        for line in txt:
            f = frx.findall(line)
            if not which:
                fields.append(f[0])
            else:
                i = int(which)
                if len(f) < i: return
                i = i-1
                fields.append(f[i])
        nz = zip(fields,txt)
        nz.sort()
        w.delete(i,j)
        int1 = i
        for z in nz:
            w.insert('%s.0' % int1,'%s\n' % z[1])
            int1 = int1 + 1
        w.setInsertPoint(ins)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.120:sortFields
    #@-node:ekr.20050920084036.117:sort...
    #@+node:ekr.20050920084036.121:swap/transpose...
    #@+node:ekr.20060529184652:swapHelper
    def swapHelper (self,w,find,ftext,lind,ltext):

        w.delete(find,'%s wordend' % find) ###
        w.insert(find,ltext)
        w.delete(lind,'%s wordend' % lind)
        w.insert(lind,ftext)
        self.swapSpots.pop()
        self.swapSpots.pop()
    #@-node:ekr.20060529184652:swapHelper
    #@+node:ekr.20050920084036.122:transposeLines
    def transposeLines (self,event):

        '''Transpose the line containing the cursor with the preceding line.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        ins = w.getInsertPoint()
        s = w.getAllText()
        if not s.strip(): return

        i,j = g.getLine(s,ins)
        line1 = s[i:j]

        self.beginCommand(undoType='transpose-lines')

        if i == 0: # Transpose the next line.
            i2,j2 = g.getLine(s,j+1)
            line2 = s[i2:j2]
            w.delete(0,j2)
            w.insert(0,line2+line1)
            w.setInsertPoint(j2-1)
        else: # Transpose the previous line.
            i2,j2 = g.getLine(s,i-1)
            line2 = s[i2:j2]
            w.delete(i2,j)
            w.insert(i2,line1+line2)
            w.setInsertPoint(j-1)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.122:transposeLines
    #@+node:ekr.20050920084036.123:swapWords
    def swapWords (self,event,swapspots):

        '''Transpose the word at the cursor with the preceding word.'''

        w = self.editWidget(event)
        if not w: return
        if g.app.gui.guiName() != 'tkinter':
            return g.es('swap-words command not ready yet',color='blue')

        s = w.getAllText()

        ### txt = w.get('insert wordstart','insert wordend') ###
        txt = ''
        if not txt: return

        i = w.index('insert wordstart') ###

        self.beginCommand(undoType='swap-words')

        if len(swapspots):
            if i > swapspots[1]:
                self.swapHelper(w,i,txt,swapspots[1],swapspots[0])
            elif i < swapspots[1]:
                self.swapHelper(w,swapspots[1],swapspots[0],i,txt)
        else:
            swapspots.append(txt)
            swapspots.append(i)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.123:swapWords
    #@+node:ekr.20060529184652.1:transposeWords (doesn't work)
    def transposeWords (self,event):

        '''Transpose the word at the cursor with the preceding word.'''

        w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType='transpose-words')
        self.swapWords(event,self.swapSpots)
        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20060529184652.1:transposeWords (doesn't work)
    #@+node:ekr.20050920084036.124:swapCharacters & transeposeCharacters
    def swapCharacters (self,event):

        k = self.k
        w = self.editWidget(event)
        if not w: return

        self.beginCommand(undoType='swap-characters')

        s = w.getAllText()
        i = w.getInsertPoint()
        if 0 < i < len(s):
            w.setAllText(s[:i-1] + s[i] + s[i-1] + s[i+1:])
            w.setSelectionRange(i,i,insert=i)

        self.endCommand(changed=True,setLabel=True)

    transposeCharacters = swapCharacters
    #@-node:ekr.20050920084036.124:swapCharacters & transeposeCharacters
    #@-node:ekr.20050920084036.121:swap/transpose...
    #@+node:ekr.20050920084036.126:tabify & untabify
    def tabify (self,event):
        '''Convert 4 spaces to tabs in the selected text.'''
        self.tabifyHelper (event,which='tabify')

    def untabify (self,event):
        '''Convert tabs to 4 spaces in the selected text.'''
        self.tabifyHelper (event,which='untabify')

    def tabifyHelper (self,event,which):

        k = self.k ; w = self.editWidget(event)
        if not w or not w.hasSelection(): return

        self.beginCommand(undoType=which)

        i,end = w.getSelectionRange()
        txt = w.getSelectedText()
        if which == 'tabify':
            pattern = re.compile(' {4,4}') # Huh?
            ntxt = pattern.sub('\t',txt)
        else:
            pattern = re.compile('\t')
            ntxt = pattern.sub('    ',txt)
        w.delete(i,end)
        w.insert(i,ntxt)
        n = i + len(ntxt)
        w.setSelectionRange(n,n,insert=n)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.126:tabify & untabify
    #@+node:ekr.20061111223516:selectAllText (leoEditCommands)
    def selectAllText (self,event):

        c = self.c 

        w = self.editWidget(event)
        if w:
            return w.selectAllText()
    #@-node:ekr.20061111223516:selectAllText (leoEditCommands)
    #@-others
#@-node:ekr.20050920084036.53:editCommandsClass
#@+node:ekr.20050920084036.161:editFileCommandsClass
class editFileCommandsClass (baseEditCommandsClass):

    '''A class to load files into buffers and save buffers to files.'''

    #@    @+others
    #@+node:ekr.20050920084036.162: ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.
    #@-node:ekr.20050920084036.162: ctor
    #@+node:ekr.20050920084036.163: getPublicCommands (editFileCommandsClass)
    def getPublicCommands (self):

        return {
            'compare-leo-files':    self.compareLeoFiles,
            'delete-file':          self.deleteFile,
            'diff':                 self.diff, 
            'insert-file':          self.insertFile,
            'make-directory':       self.makeDirectory,
            'open-outline-by-name': self.openOutlineByName,
            'remove-directory':     self.removeDirectory,
            'save-file':            self.saveFile
        }
    #@-node:ekr.20050920084036.163: getPublicCommands (editFileCommandsClass)
    #@+node:ekr.20070920104110:compareLeoFiles
    def compareLeoFiles (self,event):

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

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

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

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

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


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

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

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

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

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

        return inserted, deleted, changed
    #@-node:ekr.20070921072608:computeChangeDicts
    #@+node:ekr.20070921072910:createAllCompareClones & helper
    def createAllCompareClones(self,inserted,deleted,changed):

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

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

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

            for key in d:
                p = d.get(key)
                clone = p.clone()
                clone.moveToLastChildOf(parent)
    #@-node:ekr.20070921074410:createCompareClones
    #@-node:ekr.20070921072910:createAllCompareClones & helper
    #@+node:ekr.20070921070101:createHiddenCommander
    def createHiddenCommander(self,fileName):

        # Read the file into a hidden commander (Similar to g.openWithFileName).
        import leo.core.leoGui as leoGui
        import leo.core.leoFrame as leoFrame
        import leo.core.leoCommands as leoCommands

        nullGui = leoGui.nullGui('nullGui')
        frame = leoFrame.nullFrame('nullFrame',nullGui,useNullUndoer=True)
        c2 = leoCommands.Commands(frame,fileName)
        frame.c = c2
        frame.tree.c = c2
        theFile,c2.isZipped = g.openLeoOrZipFile(fileName)
        if theFile:
            c2.fileCommands.open(theFile,fileName,readAtFileNodesFlag=True,silent=True)
            return c2
        else:
            return None
    #@nonl
    #@-node:ekr.20070921070101:createHiddenCommander
    #@+node:ekr.20070921070101.1:createFileDict
    def createFileDict (self,c):

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

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

        for d,kind in (
            (inserted,'inserted (only in %s)' % (fileName1)),
            (deleted, 'deleted  (only in %s)' % (fileName2)),
            (changed, 'changed'),
        ):
            g.pr('\n',kind)
            for key in d:
                p = d.get(key)
                if g.isPython3:
                    g.pr('%-32s %s' % (key,p.h))
                else:
                    g.pr('%-32s %s' % (key,g.toEncodedString(p.h,'ascii')))
    #@-node:ekr.20070921072608.1:dumpCompareNodes
    #@-node:ekr.20070920104110:compareLeoFiles
    #@+node:ekr.20050920084036.164:deleteFile
    def deleteFile (self,event):

        '''Prompt for the name of a file and delete it.'''

        k = self.k ; state = k.getState('delete_file')

        if state == 0:
            prefix = 'Delete File: '
            k.setLabelBlue('%s%s%s' % (prefix,os.getcwd(),os.sep))
            k.getArg(event,'delete_file',1,self.deleteFile,prefix=prefix)
        else:
            k.keyboardQuit(event)
            k.clearState()
            try:
                os.remove(k.arg)
                k.setLabel('Deleted: %s' % k.arg)
            except Exception:
                k.setLabel('Not Deleted: %s' % k.arg)
    #@-node:ekr.20050920084036.164:deleteFile
    #@+node:ekr.20050920084036.165:diff (revise)
    def diff (self,event):

        '''Creates a node and puts the diff between 2 files into it.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return
        fn = self.getReadableTextFile()
        if not fn: return
        fn2 = self.getReadableTextFile()
        if not fn2: return
        s1,e = g.readFileIntoString(fn)
        if s1 is None: return
        s2,e = g.readFileIntoString(fn2)
        if s2 is None: return

        ### self.switchToBuffer(event,"*diff* of ( %s , %s )" % (name,name2))
        data = difflib.ndiff(s1,s2)
        idata = []
        for z in data:
            idata.append(z)
        w.delete(0,'end')
        w.insert(0,''.join(idata))
    #@-node:ekr.20050920084036.165:diff (revise)
    #@+node:ekr.20050920084036.166:getReadableTextFile
    def getReadableTextFile (self):

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

        return fn
    #@-node:ekr.20050920084036.166:getReadableTextFile
    #@+node:ekr.20050920084036.167:insertFile
    def insertFile (self,event):

        '''Prompt for the name of a file and put the selected text into it.'''

        k = self.k ; c = k.c ; w = self.editWidget(event)
        if not w: return

        fn = self.getReadableTextFile()
        if not fn: return

        s,e = g.readFileIntoString(fn)
        if s is None: return

        self.beginCommand(undoType='insert-file')
        i = w.getInsertPoint()
        w.insert(i,s)
        w.seeInsertPoint()
        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.167:insertFile
    #@+node:ekr.20050920084036.168:makeDirectory
    def makeDirectory (self,event):

        '''Prompt for the name of a directory and create it.'''

        k = self.k ; state = k.getState('make_directory')

        if state == 0:
            prefix = 'Make Directory: '
            k.setLabelBlue('%s%s%s' % (prefix,os.getcwd(),os.sep))
            k.getArg(event,'make_directory',1,self.makeDirectory,prefix=prefix)
        else:
            k.keyboardQuit(event)
            k.clearState()
            try:
                os.mkdir(k.arg)
                k.setLabel("Created: %s" % k.arg)
            except Exception:
                k.setLabel("Not Create: %s" % k.arg)
    #@-node:ekr.20050920084036.168:makeDirectory
    #@+node:ekr.20060419123128:open-outline-by-name
    def openOutlineByName (self,event):

        '''Prompt for the name of a Leo outline and open it.'''

        c = self.c ; k = self.k ; fileName = ''.join(k.givenArgs)

        if fileName:
            g.openWithFileName(fileName,c)
        else:
            k.setLabelBlue('Open Leo Outline: ',protect=True)
            k.getFileName(event,handler=self.openOutlineByNameFinisher)

    def openOutlineByNameFinisher (self,event):

        c = self.c ; k = self.k ; fileName = k.arg

        k.resetLabel()
        if fileName and g.os_path_exists(fileName) and not g.os_path_isdir(fileName):
            g.openWithFileName(fileName,c)
    #@-node:ekr.20060419123128:open-outline-by-name
    #@+node:ekr.20050920084036.169:removeDirectory
    def removeDirectory (self,event):

        '''Prompt for the name of a directory and delete it.'''

        k = self.k ; state = k.getState('remove_directory')

        if state == 0:
            prefix = 'Remove Directory: '
            k.setLabelBlue('%s%s%s' % (prefix,os.getcwd(),os.sep))
            k.getArg(event,'remove_directory',1,self.removeDirectory,prefix=prefix)
        else:
            k.keyboardQuit(event)
            k.clearState()
            try:
                os.rmdir(k.arg)
                k.setLabel('Removed: %s' % k.arg)
            except Exception:
                k.setLabel('Not Remove: %s' % k.arg)
    #@-node:ekr.20050920084036.169:removeDirectory
    #@+node:ekr.20050920084036.170:saveFile
    def saveFile (self,event):

        '''Prompt for the name of a file and put the body text of the selected node into it..'''

        w = self.editWidget(event)
        if not w: return

        fileName = g.app.gui.runSaveFileDialog(
            initialfile = None,
            title='save-file',
            filetypes = [("Text","*.txt"), ("All files","*")],
            defaultextension = ".txt")

        if not fileName: return

        try:
            s = w.getAllText()
            f = open(fileName,'w')
            f.write(s)
            f.close()
        except IOError:
            g.es('can not create',fileName)
    #@-node:ekr.20050920084036.170:saveFile
    #@-others
#@-node:ekr.20050920084036.161:editFileCommandsClass
#@+node:ekr.20060205164707:helpCommandsClass
class helpCommandsClass (baseEditCommandsClass):

    '''A class to load files into buffers and save buffers to files.'''

    #@    @+others
    #@+node:ekr.20060205165501:getPublicCommands (helpCommands)
    def getPublicCommands (self):

        return {
            'help-for-minibuffer':      self.helpForMinibuffer,
            'help-for-command':         self.helpForCommand,
            'apropos-autocompletion':   self.aproposAutocompletion,
            'apropos-bindings':         self.aproposBindings,
            'apropos-debugging-commands': self.aproposDebuggingCommands,
            'apropos-find-commands':    self.aproposFindCommands,
            'print-settings':           self.printSettings,
            'python-help':              self.pythonHelp,
        }
    #@-node:ekr.20060205165501:getPublicCommands (helpCommands)
    #@+node:ekr.20051014170754:helpForMinibuffer
    def helpForMinibuffer (self,event=None):

        '''Print a messages telling you how to get started with Leo.'''

        # A bug in Leo: triple quotes puts indentation before each line.
        c = self.c
        s = '''
    The mini-buffer is intended to be like the Emacs buffer:

    full-command: (default shortcut: Alt-x) Puts the focus in the minibuffer. Type a
    full command name, then hit <Return> to execute the command. Tab completion
    works, but not yet for file names.

    quick-command-mode (default shortcut: Alt-x). Like Emacs Control-C. This mode is
    defined in leoSettings.leo. It is useful for commonly-used commands.

    universal-argument (default shortcut: Alt-u). Like Emacs Ctrl-u. Adds a repeat
    count for later command. Ctrl-u 999 a adds 999 a's. Many features remain
    unfinished.

    keyboard-quit (default shortcut: Ctrl-g) Exits any minibuffer mode and puts
    the focus in the body pane.

    Use the help-for-command command to see documentation for a particular command.
    '''

        s = g.adjustTripleString(s,c.tab_width)
            # Remove indentation from indentation of this function.
        # s = s % (shortcuts[0],shortcuts[1],shortcuts[2],shortcuts[3])

        if not g.app.unitTesting:
            g.es_print('',s)
    #@-node:ekr.20051014170754:helpForMinibuffer
    #@+node:ekr.20060417203717:helpForCommand
    def helpForCommand (self,event):

        '''Prompts for a command name and prints the help message for that command.'''

        k = self.k
        k.fullCommand(event,help=True,helpHandler=self.helpForCommandFinisher)

    def helpForCommandFinisher (self,commandName):

        c = self.c
        bindings = self.getBindingsForCommand(commandName)
        func = c.commandsDict.get(commandName)
        if func and func.__doc__:
            s = ''.join([
                g.choose(line.strip(),line.lstrip(),'\n')
                    for line in g.splitLines(func.__doc__)])
        else:
            s = 'no docstring'
        g.es('','%s:%s\n%s\n' % (commandName,bindings,s),color='blue')

    def getBindingsForCommand(self,commandName):

        c = self.c ; k = c.k ; d = k.bindingsDict
        data = [] ; n1 = 4 ; n2 = 20
        for key in sorted(d):
            bunchList = d.get(key,[])
            for b in bunchList:
                if b.commandName == commandName:
                    pane = g.choose(b.pane=='all','',' %s:' % (b.pane))
                    s1 = pane
                    s2 = k.prettyPrintKey(key,brief=True)
                    s3 = b.commandName
                    n1 = max(n1,len(s1))
                    n2 = max(n2,len(s2))
                    data.append((s1,s2,s3),)

        data.sort(key=lambda x: x[1])
            # key is a function that extracts args.

        return ','.join(['%s %s' % (s1,s2) for s1,s2,s3 in data])
    #@nonl
    #@-node:ekr.20060417203717:helpForCommand
    #@+node:ekr.20060226131603.1:aproposAutocompletion
    # @pagewidth 40

    def aproposAutocompletion (self,event=None):

        '''Prints a discussion of autocompletion.'''

        c = self.c ; s = '''

    This documentation describes both
    autocompletion and calltips.

    Typing a period when @language python is
    in effect starts autocompletion. Typing
    '(' during autocompletion shows the
    calltip. Typing Return or Control-g
    (keyboard-quit) exits autocompletion or
    calltips.

    === Autocompletion

    Autocompletion shows what may follow a
    period in code. For example, after
    typing g. Leo will show a list of all
    the global functions in leoGlobals.py.
    Autocompletion works much like tab
    completion in the minibuffer. Unlike the
    minibuffer, the presently selected
    completion appears directly in the body
    pane.

    A leading period brings up 'Autocomplete
    Modules'. (The period goes away.) You
    can also get any module by typing its
    name. If more than 25 items would appear
    in the Autocompleter tab, Leo shows only
    the valid starting characters. At this
    point, typing an exclamation mark shows
    the complete list. Thereafter, typing
    further exclamation marks toggles
    between full and abbreviated modes.

    If x is a list 'x.!' shows all its
    elements, and if x is a Python
    dictionary, 'x.!' shows list(x.keys()).
    For example, 'sys.modules.!' Again,
    further exclamation marks toggles
    between full and abbreviated modes.

    During autocompletion, typing a question
    mark shows the docstring for the object.
    For example: 'g.app?' shows the
    docstring for g.app. This doesn't work
    (yet) directly for Python globals, but
    '__builtin__.f?' does. Example:
    '__builtin__.pow?' shows the docstring
    for pow.

    Autocompletion works in the Find tab;
    you can use <Tab> to cycle through the
    choices. The 'Completion' tab appears
    while you are doing this; the Find tab
    reappears once the completion is
    finished.

    === Calltips

    Calltips appear after you type an open
    parenthesis in code. Calltips shows the
    expected arguments to a function or
    method. Calltips work for any Python
    function or method, including Python's
    global function. Examples:

    a) g.toUnicode(
       gives:
       g.toUnicode(s,encoding, reportErrors=False

    b) c.widgetWantsFocusNow
       gives:
       c.widgetWantsFocusNow(w

    c) reduce(
       gives:
       reduce(function, sequence[,initial]) -> value

    The calltips appear directly in the text
    and the argument list is highlighted so
    you can just type to replace it. The
    calltips appear also in the status line
    for reference after you have started to
    replace the args.

    Options

    Both autocompletion and calltips are
    initially enabled or disabled by the
    enable_autocompleter_initially and
    enable_calltips_initially settings in
    leoSettings.leo. You may enable or
    disable these features at any time with
    these commands: enable-autocompleter,
    enable-calltips, disable-autocompleter
    and disable-calltips. '''

        if not g.app.unitTesting:
            # Remove indentation from indentation of this function.
            s = g.adjustTripleString(s,c.tab_width)
            g.es('',s)
    #@-node:ekr.20060226131603.1:aproposAutocompletion
    #@+node:ekr.20060205170335:aproposBindings
    # @pagewidth 40

    def aproposBindings (self,event=None):

        '''Prints a discussion of keyboard bindings.'''

        c = self.c
        s = '''

    A shortcut specification has the form:

    command-name = shortcutSpecifier

    or

    command-name ! pane = shortcutSpecifier

    The first form creates a binding for all
    panes except the minibuffer. The second
    form creates a binding for one or more
    panes. The possible values for 'pane'
    are:

    pane    bound panes
    ----    -----------
    all     body,log,tree
    body    body
    log     log
    mini    minibuffer
    text    body,log
    tree    tree

    You may use None as the specifier.
    Otherwise, a shortcut specifier consists
    of a head followed by a tail. The head
    may be empty, or may be a concatenation
    of the following: (All entries in each
    row are equivalent).

    Shift+ Shift-
    Alt+ or Alt-
    Control+, Control-, Ctrl+ or Ctrl-

    Notes:

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

    2. The Shift- (or Shift+) prefix can be
       applied *only* to letters or
       multi-letter tails. Leo will ignore
       (with a warning) the shift prefix
       applied to other single letters,
       e.g., Ctrl-Shift-(

    3. The case of letters prefixed by
       Ctrl-, Alt-, Key- or Shift- is *not*
       significant.

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

    a, Key-a, Key-A
    A, Shift-A
    Alt-a, Alt-A
    Alt-A, Alt-Shift-a, Alt-Shift-A
    Ctrl-a, Ctrl-A
    Ctrl-A, Ctrl-Shift-a, Ctrl-Shift-A
    !, Key-!,Key-exclam,exclam
    '''

        s = g.adjustTripleString(s,c.tab_width)
            # Remove indentation from indentation of this function.

        if not g.app.unitTesting:
            g.es('',s)
    #@-node:ekr.20060205170335:aproposBindings
    #@+node:ekr.20070501092655:aproposDebuggingCommands
    def aproposDebuggingCommands (self,event=None):

        '''Prints a discussion of of Leo's debugging commands.'''

        c = self.c

        #@    << define s >>
        #@+node:ekr.20070501092655.1:<< define s >>
        # @pagewidth 40

        s = '''
        The following commands are useful for debugging:

        collect-garbage:   Invoke the garbage collector.
        debug:             Start an external debugger in another process.
        disable-gc-trace:  Disable tracing of the garbage collector.
        dump-all-objects:  Print a summary of all existing Python objects.
        dump-new-objects:  Print a summary of all newly-created Python objects.
        enable-gc-trace:   Enable tracing of the garbage collector.
        free-tree-widgets: Free all widgets used in Leo's outline pane.
        print-focus:       Print information about the requested focus.
        print-stats:       Print statistics about existing Python objects.
        print-gc-summary:  Print a brief summary of all Python objects.
        run-unit-tests:    Run unit tests in the presently selected tree.
        verbose-dump-objects: Print a more verbose listing of all existing Python objects.

        Leo also has many debugging settings that enable and disable traces.
        For details, see the node: @settings-->Debugging in leoSettings.leo.
        '''
        #@-node:ekr.20070501092655.1:<< define s >>
        #@nl

        # Remove indentation from s: a workaround of a Leo bug.
        s = g.adjustTripleString(s,c.tab_width)

        if not g.app.unitTesting:
            g.es('',s)
    #@-node:ekr.20070501092655:aproposDebuggingCommands
    #@+node:ekr.20060205170335.1:aproposFindCommands
    def aproposFindCommands (self, event=None):

        '''Prints a discussion of of Leo's find commands.'''

        c = self.c

        #@    << define s >>
        #@+node:ekr.20060209082023.1:<< define s >>
        #@@pagewidth 40

        s = '''
        Note: all bindings shown are the default
        bindings for these commands. You may
        change any of these bindings using
        @shortcuts nodes in leoSettings.leo.

        === Settings

        leoSettings.leo now contains several
        settings related to the Find tab:

        @bool show_only_find_tab_options = True

          When True (recommended), the Find tab
          does not show the 'Find', 'Change',
          'Change, Then Find', 'Find All' and
          'Change All' buttons.

        @bool minibufferSearchesShowFindTab = True

          When True, Leo shows the Find tab when
          executing most of the commands
          discussed below.

        === Basic find commands

        open-find-tab

          Makes the Find tab visible. The Find
          tab does **not** need to be visible to
          execute any search command discussed
          below.

        hide-find-tab

          Hides the Find tab, but retains all
          the present settings.

        search-with-present-options (Ctrl-F)

          Prompts for a search string. Typing
          the <Return> key puts the search
          string in the Find tab and executes a
          search based on all the settings in
          the Find tab. This is a recommended
          default search command.

        show-search-options

          Shows the present search options in
          the status line. This command also
          makes the Find tab visible.

        find-next (F3)

          Like search-with-present-options,
          except that it uses the search string
          in the find-tab. Recommended as the
          default 'search again' command.

        find-previous (F2)

          Repeats the command specified by the
          Find tab, but in reverse.

        find-again

          Like find-next if a search pattern is
          not '<find pattern here>'. Otherwise,
          like search-with-present-options.

        === Setting find options

        Several minibuffer commands toggle the
        checkboxes and radio buttons in the Find
        tab, and thus affect the operation of
        the search-with-present-options command.
        You may bind these commands to keys or
        toggle these options in a mode.

        These commands toggle checkboxes:

        toggle-find-ignore-case-option
        toggle-find-in-body-option
        toggle-find-in-headline-option
        toggle-find-mark-changes-option
        toggle-find-mark-finds-option
        toggle-find-regex-option
        toggle-find-reverse-option
        toggle-find-word-option
        toggle-find-wrap-around-option

        These commands set radio buttons:

        set-find-everywhere,
        set-find-node-only, and
        set-find-suboutline-only.

        enter-find-options-mode (Ctrl-Shift-F)

        enters a mode in which you may change
        all checkboxes and radio buttons in the
        Find tab with plain keys. As always, you
        can use the mode-help (Tab) command to
        see a list of key bindings in effect for
        the mode.

        === Search commands with side effects

        The following commands set an option in
        the Find tab, then work exactly like the
        search-with-present-options command.

        - search-backward and search-forward set
          the 'Whole Word' checkbox to False.

        - word-search-backward and
          word-search-forward set the 'Whole
          Word' checkbox to True.

        - re-search-forward and re-search-backward
          set the 'Regexp' checkbox to True.

        === Find all commands

        find-all

          Prints all matches in the log pane.

        clone-find-all

          Replaces the previous 'Clone Find'
          checkbox. It prints all matches in the
          log pane, and creates a node at the
          beginning of the outline containing
          clones of all nodes containing the
          'find' string. Only one clone is made
          of each node, regardless of how many
          clones the node has, or of how many
          matches are found in each node.

        Note: the radio buttons in the Find tab
        (Entire Outline, Suboutline Only and
        Node only) control how much of the
        outline is affected by the find-all and
        clone-find-all commands.

        === Search and replace commands

        replace-string

          Prompts for a search string. Type
          <Return> to end the search string. The
          command will then prompt for the
          replacement string. Typing a second
          <Return> key will place both strings
          in the Find tab and executes a
          **find** command, that is,
          search-with-present-options.

        So the only difference between
        replace-string and
        search-with-present-options is that
        replace-string has the side effect of
        setting 'change' string in the Find tab.
        However, this is an extremely useful
        side effect, because of the following
        commands...

        change (Ctrl-=)

          Replaces the selected text with the
          'change' text in the Find tab.

        change-then-find (Ctrl--)

          Replaces the selected text with the
          'change' text in the Find tab, then
          executes the find command again.

        find-next, change and change-then-find
        can simulate any kind of query-replace
        command.

        change-all

          Changes all occurrences of the 'find'
          text with the 'change' text.
          Important: the radio buttons in the
          Find tab (Entire Outline, Suboutline
          Only and Node only) control how much
          of the outline is affected by this
          command.

        === Incremental search commands

        Here are Leo's incremental find commands:

        isearch-backward (Alt-R)
        isearch-backward-regexp
        isearch-forward (Alt-S)
        isearch-forward-regexp

        You may use backspace to backtrack. To
        repeat an incremental search, type the
        shortcut for that command again.'''
        #@-node:ekr.20060209082023.1:<< define s >>
        #@nl

        # Remove indentation from s: a workaround of a Leo bug.
        s = g.adjustTripleString(s,c.tab_width)

        if not g.app.unitTesting:
            g.es('',s)
    #@-node:ekr.20060205170335.1:aproposFindCommands
    #@+node:ekr.20060602154458:pythonHelp
    def pythonHelp (self,event=None):

        '''Prompt for a arg for Python's help function, and put it to the log pane.'''

        c = self.c ; k = c.k ; tag = 'python-help' ; state = k.getState(tag)

        if state == 0:
            c.frame.minibufferWantsFocus()
            k.setLabelBlue('Python help: ',protect=True)
            k.getArg(event,tag,1,self.pythonHelp)
        else:
            k.clearState()
            k.resetLabel()
            s = k.arg.strip()
            if s:
                g.redirectStderr()
                g.redirectStdout()
                try: help(str(s))
                except Exception: pass
                g.restoreStderr()
                g.restoreStdout()
    #@-node:ekr.20060602154458:pythonHelp
    #@+node:ekr.20070418074444:printSettings
    def printSettings (self,event=None):

        g.app.config.printSettings(self.c)
    #@-node:ekr.20070418074444:printSettings
    #@-others
#@-node:ekr.20060205164707:helpCommandsClass
#@+node:ekr.20050920084036.171:keyHandlerCommandsClass (add docstrings)
class keyHandlerCommandsClass (baseEditCommandsClass):

    '''User commands to access the keyHandler class.'''

    #@    @+others
    #@+node:ekr.20050920084036.172: ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.
    #@-node:ekr.20050920084036.172: ctor
    #@+node:ekr.20050920084036.173:getPublicCommands (keyHandler)
    def getPublicCommands (self):

        k = self.k

        if k:
            return {
                'auto-complete':            k.autoCompleter.autoComplete,
                'auto-complete-force':      k.autoCompleter.autoCompleteForce,
                'digit-argument':           k.digitArgument,
                'disable-autocompleter':    k.autoCompleter.disableAutocompleter,
                'disable-calltips':         k.autoCompleter.disableCalltips,
                'enable-autocompleter':     k.autoCompleter.enableAutocompleter,
                'enable-calltips':          k.autoCompleter.enableCalltips,
                'exit-named-mode':          k.exitNamedMode,
                'full-command':             k.fullCommand, # For menu.
                'hide-mini-buffer':         k.hideMinibuffer,
                'mode-help':                k.modeHelp,
                'negative-argument':        k.negativeArgument,
                'number-command':           k.numberCommand,
                'number-command-0':         k.numberCommand0,
                'number-command-1':         k.numberCommand1,
                'number-command-2':         k.numberCommand2,
                'number-command-3':         k.numberCommand3,
                'number-command-4':         k.numberCommand4,
                'number-command-5':         k.numberCommand5,
                'number-command-6':         k.numberCommand6,
                'number-command-7':         k.numberCommand7,
                'number-command-8':         k.numberCommand8,
                'number-command-9':         k.numberCommand9,
                'print-bindings':           k.printBindings,
                'print-commands':           k.printCommands,
                'propagate-key-event':      k.propagateKeyEvent,
                'repeat-complex-command':   k.repeatComplexCommand,
                # 'scan-for-autocompleter':   k.autoCompleter.scan,
                'set-command-state':        k.setCommandState,
                'set-insert-state':         k.setInsertState,
                'set-overwrite-state':      k.setOverwriteState,
                'show-calltips':            k.autoCompleter.showCalltips,
                'show-calltips-force':      k.autoCompleter.showCalltipsForce,
                'show-mini-buffer':         k.showMinibuffer,
                'toggle-autocompleter':     k.autoCompleter.toggleAutocompleter,
                'toggle-calltips':          k.autoCompleter.toggleCalltips,
                'toggle-mini-buffer':       k.toggleMinibuffer,
                'toggle-input-state':       k.toggleInputState,
                'universal-argument':       k.universalArgument,
            }
        else:
            return {}
    #@-node:ekr.20050920084036.173:getPublicCommands (keyHandler)
    #@-others
#@-node:ekr.20050920084036.171:keyHandlerCommandsClass (add docstrings)
#@+node:ekr.20050920084036.174:killBufferCommandsClass
class killBufferCommandsClass (baseEditCommandsClass):

    '''A class to manage the kill buffer.'''

    #@    @+others
    #@+node:ekr.20050920084036.175: ctor & finishCreate
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.addWsToKillRing = c.config.getBool('add-ws-to-kill-ring')
        self.killBuffer = [] # May be changed in finishCreate.
        self.kbiterator = self.iterateKillBuffer()
        self.last_clipboard = None # For interacting with system clipboard.
        self.lastYankP = None # The position of the last item returned by iterateKillBuffer.
        self.reset = None
            # None, or the index of the next item to be returned in killBuffer by iterateKillBuffer.

    def finishCreate (self):

        baseEditCommandsClass.finishCreate(self)
            # Call the base finishCreate.
            # This sets self.k

        if self.k and self.k.useGlobalKillbuffer:
            self.killBuffer = leoKeys.keyHandlerClass.global_killbuffer
    #@-node:ekr.20050920084036.175: ctor & finishCreate
    #@+node:ekr.20050920084036.176: getPublicCommands
    def getPublicCommands (self):

        return {
            'backward-kill-sentence':   self.backwardKillSentence,
            'backward-kill-word':       self.backwardKillWord,
            'clear-kill-ring':          self.clearKillRing,
            'kill-line':                self.killLine,
            'kill-word':                self.killWord,
            'kill-sentence':            self.killSentence,
            'kill-region':              self.killRegion,
            'kill-region-save':         self.killRegionSave,
            'kill-ws':                  self.killWs,
            'yank':                     self.yank,
            'yank-pop':                 self.yankPop,
            'zap-to-character':         self.zapToCharacter,
        }
    #@-node:ekr.20050920084036.176: getPublicCommands
    #@+node:ekr.20050920084036.183:addToKillBuffer
    def addToKillBuffer (self,text):

        '''Insert the text into the kill buffer if force is True or
        the text contains something other than whitespace.'''

        if self.addWsToKillRing or text.strip():
            self.killBuffer = [z for z in self.killBuffer if z != text]
            self.killBuffer.insert(0,text)
    #@-node:ekr.20050920084036.183:addToKillBuffer
    #@+node:ekr.20050920084036.181:backwardKillSentence
    def backwardKillSentence (self,event):

        '''Kill the previous sentence.'''

        w = self.editWidget(event)
        if not w: return

        s = w.getAllText()
        ins = w.getInsertPoint()
        i = s.rfind('.',ins)
        if i == -1: return

        undoType='backward-kill-sentence'

        self.beginCommand(undoType=undoType)

        i2 = s.rfind('.',0,i) + 1
        self.kill(event,i2,i+1,undoType=undoType)
        self.c.frame.body.forceFullRecolor()
        w.setInsertPoint(i2)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.181:backwardKillSentence
    #@+node:ekr.20050920084036.180:backwardKillWord & killWord
    def backwardKillWord (self,event):
        '''Kill the previous word.'''
        c = self.c ; e = c.editCommands
        self.beginCommand(undoType='backward-kill-word')
        e.backwardWord(event)
        self.killWordHelper(event,'back')

    def killWord (self,event):
        '''Kill the word containing the cursor.'''
        self.beginCommand(undoType='kill-word')
        self.killWordHelper(event,'forward')

    def killWordHelper(self,event,direction):
        c = self.c ; e = c.editCommands ; w = e.editWidget(event)
        # self.killWs(event)
        e.extendToWord(event,direction)
        i,j = w.getSelectionRange()
        self.kill(event,i,j,undoType = None)
        c.frame.body.forceFullRecolor()
        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.180:backwardKillWord & killWord
    #@+node:ekr.20051216151811:clearKillRing
    def clearKillRing (self,event=None):

        '''Clear the kill ring.'''

        self.killBuffer = []
    #@-node:ekr.20051216151811:clearKillRing
    #@+node:ekr.20050920084036.185:getClipboard
    def getClipboard (self):

        '''Return the contents of the clipboard.'''

        try:
            ctxt = g.app.gui.getTextFromClipboard()
            if not self.killBuffer or ctxt != self.last_clipboard:
                self.last_clipboard = ctxt
                if not self.killBuffer or self.killBuffer [0] != ctxt:
                    return ctxt
        except Exception:
            g.es_exception()

        return None
    #@-node:ekr.20050920084036.185:getClipboard
    #@+node:ekr.20050920084036.184:iterateKillBuffer
    class killBuffer_iter_class:

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

        #@    @+others
        #@+node:ekr.20071003160252.1:__init__ & __iter__
        def __init__(self,c):

            # g.trace('iterateKillBuffer.__init')
            self.c = c
            self.index = 0 # The index of the next item to be returned.

        def __iter__(self):

            return self
        #@-node:ekr.20071003160252.1:__init__ & __iter__
        #@+node:ekr.20071003160252.2:next
        def next(self):

            commands = self.c.killBufferCommands
            aList = commands.killBuffer

            # g.trace(g.listToString([repr(z) for z in aList]))

            if not aList:
                self.index = 0
                return None

            if commands.reset is None:
                i = self.index
            else:
                i = commands.reset
                commands.reset = None

            if i < 0 or i >= len(aList): i = 0
            # g.trace(i)
            val = aList[i]
            self.index = i + 1
            return val

        __next__ = next
        #@nonl
        #@-node:ekr.20071003160252.2:next
        #@-others

    def iterateKillBuffer (self):

        return self.killBuffer_iter_class(self.c)
    #@-node:ekr.20050920084036.184:iterateKillBuffer
    #@+node:ekr.20050920084036.178:kill
    def kill (self,event,frm,to,undoType=None):

        '''A helper method for all kill commands.'''

        k = self.k
        w = self.editWidget(event)
        if not w: return

        s = w.get(frm,to)
        if undoType: self.beginCommand(undoType=undoType)
        self.addToKillBuffer(s)
        g.app.gui.replaceClipboardWith(s)
        w.delete(frm,to)
        w.setInsertPoint(frm)
        if undoType:
            self.c.frame.body.forceFullRecolor()
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.178:kill
    #@+node:ekr.20071003183657:KillLine
    def killLine (self,event):
        '''Kill the line containing the cursor.'''
        w = self.editWidget(event)
        if not w: return
        s = w.getAllText()
        ins = w.getInsertPoint()
        i,j = g.getLine(s,ins)
        # g.trace(i,j,ins,len(s),repr(s[i:j]))
        if ins >= len(s) and g.match(s,j-1,'\n'): # Kill the trailing newline.
            i = max(0,len(s)-1)
            j = len(s)
        elif j > i+1 and g.match(s,j-1,'\n'): # Kill the line, but not the newline.
            j -= 1
        else: # Kill the newline.
            pass
        self.kill(event,i,j,undoType='kill-line')
    #@-node:ekr.20071003183657:KillLine
    #@+node:ekr.20050920084036.182:killRegion & killRegionSave & helper
    def killRegion (self,event):
        '''Kill the text selection.'''
        self.killRegionHelper(event,deleteFlag=True)

    def killRegionSave (self,event):
        '''Add the selected text to the kill ring, but do not delete it.'''
        self.killRegionHelper(event,deleteFlag=False)

    def killRegionHelper (self,event,deleteFlag):

        w = self.editWidget(event)
        if not w: return
        i,j = w.getSelectionRange()
        if i == j: return
        s = w.getSelectedText()
        if deleteFlag:
            self.beginCommand(undoType='kill-region')
            w.delete(i,j)
            self.c.frame.body.forceFullRecolor()
            self.endCommand(changed=True,setLabel=True)
        self.addToKillBuffer(s)
        g.app.gui.replaceClipboardWith(s)
        # self.removeRKeys(w)
    #@-node:ekr.20050920084036.182:killRegion & killRegionSave & helper
    #@+node:ekr.20050930095323.1:killSentence
    def killSentence (self,event):

        '''Kill the sentence containing the cursor.'''

        w = self.editWidget(event)
        if not w: return
        s = w.getAllText()
        ins = w.getInsertPoint()
        i = s.find('.',ins)
        if i == -1: return

        undoType='kill-sentence'

        self.beginCommand(undoType=undoType)

        i2 = s.rfind('.',0,ins) + 1
        self.kill(event,i2,i+1,undoType=undoType)
        self.c.frame.body.forceFullRecolor()
        w.setInsertPoint(i2)

        self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050930095323.1:killSentence
    #@+node:ekr.20050930100733:killWs
    def killWs (self,event,undoType='kill-ws'):

        '''Kill whitespace.'''

        ws = ''
        w = self.editWidget(event)
        if not w: return
        s = w.getAllText()
        i = j = ins = w.getInsertPoint()

        while i >= 0 and s[i] in (' ','\t'):
            i-= 1
        if i < ins: i += 1

        while j < len(s) and s[j] in (' ','\t'):
            j += 1

        if j > i:
            ws = s[i:j]
            # g.trace(i,j,repr(ws))
            w.delete(i,j)
            if undoType: self.beginCommand(undoType=undoType)
            if self.addWsToKillRing:
                self.addToKillBuffer(ws)
            if undoType: self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050930100733:killWs
    #@+node:ekr.20050930091642.1:yank
    def yank (self,event,pop=False):

        '''yank: insert the first entry of the kill ring.
        yank-pop: insert the next entry of the kill ring.
        '''

        c = self.c ; w = self.editWidget(event)
        if not w: return
        current = c.p
        if not current: return
        text = w.getAllText()
        i, j = w.getSelectionRange()
        clip_text = self.getClipboard()
        if not self.killBuffer and not clip_text: return

        undoType = g.choose(pop,'yank-pop','yank')
        self.beginCommand(undoType=undoType)
        try:
            if not pop or self.lastYankP and self.lastYankP != current:
                self.reset = 0
            s = self.kbiterator.next()
            if s is None: s = clip_text or ''
            if i != j: w.deleteTextSelection()
            if s != s.lstrip(): # s contains leading whitespace.
                i2,j2 = g.getLine(text,i)
                k = g.skip_ws(text,i2)
                if i2 < i <= k:
                    # Replace the line's leading whitespace by s's leading whitespace.
                    w.delete(i2,k)
                    i = i2
            w.insert(i,s)
            w.setSelectionRange(i,i+len(s),insert=i+len(s))
            self.lastYankP = current.copy()
            c.frame.body.forceFullRecolor()
        finally:
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050930091642.1:yank
    #@+node:ekr.20050930091642.2:yankPop
    def yankPop (self,event):

        '''Insert the next entry of the kill ring.'''

        self.yank(event,pop=True)

    #@-node:ekr.20050930091642.2:yankPop
    #@+node:ekr.20050920084036.128:zapToCharacter
    def zapToCharacter (self,event):

        '''Kill characters from the insertion point to a given character.'''

        k = self.k ; w = self.editWidget(event)
        if not w: return

        state = k.getState('zap-to-char')
        if state == 0:
            k.setLabelBlue('Zap To Character: ',protect=True)
            k.setState('zap-to-char',1,handler=self.zapToCharacter)
        else:
            ch = event and event.char or ' '
            k.resetLabel()
            k.clearState()
            s = w.getAllText()
            ins = w.getInsertPoint()
            i = s.find(ch,ins)
            if i == -1: return
            self.beginCommand(undoType='zap-to-char')
            self.addToKillBuffer(s[ins:i])
            g.app.gui.replaceClipboardWith(s[ins:i]) # Support for proper yank.
            w.setAllText(s[:ins] + s[i:])
            w.setInsertPoint(ins)
            self.endCommand(changed=True,setLabel=True)
    #@-node:ekr.20050920084036.128:zapToCharacter
    #@-others
#@-node:ekr.20050920084036.174:killBufferCommandsClass
#@+node:ekr.20050920084036.186:leoCommandsClass (add docstrings)
class leoCommandsClass (baseEditCommandsClass):

    #@    @+others
    #@+node:ekr.20050920084036.187: ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.
    #@-node:ekr.20050920084036.187: ctor
    #@+node:ekr.20050920084036.188:leoCommands.getPublicCommands
    def getPublicCommands (self):

        '''(leoCommands) Return a dict of the 'legacy' Leo commands.'''

        k = self.k ; d2 = {}

        #@    << define dictionary d of names and Leo commands >>
        #@+node:ekr.20050920084036.189:<< define dictionary d of names and Leo commands >>
        c = self.c ; f = c.frame

        d = {
            'abort-edit-headline':          f.abortEditLabelCommand,
            'about-leo':                    c.about,
            'add-comments':                 c.addComments,     
            'beautify':                     c.beautifyPythonCode,
            'beautify-all':                 c.beautifyAllPythonCode,
            'beautify-tree':                c.beautifyPythonTree,
            'cascade-windows':              f.cascade,
            'check-all-python-code':        c.checkAllPythonCode,
            'check-derived-file':           c.atFileCommands.checkDerivedFile,
            'check-leo-file':               c.fileCommands.checkLeoFile,
            'check-outline':                c.checkOutline,
            'check-python-code':            c.checkPythonCode,
            'clean-recent-files':           c.cleanRecentFiles,
            'clear-recent-files':           c.clearRecentFiles,
            'clone-node':                   c.clone,
            'close-window':                 c.close,
            'contract-all':                 c.contractAllHeadlines,
            'contract-all-other-nodes':     c.contractAllOtherNodes,
            'contract-node':                c.contractNode,
            'contract-or-go-left':          c.contractNodeOrGoToParent,
            'contract-parent':              c.contractParent,
            'convert-all-blanks':           c.convertAllBlanks,
            'convert-all-tabs':             c.convertAllTabs,
            'convert-blanks':               c.convertBlanks,
            'convert-tabs':                 c.convertTabs,
            'copy-node':                    c.copyOutline,
            'copy-text':                    f.copyText,
            'cut-node':                     c.cutOutline,
            'cut-text':                     f.cutText,
            'de-hoist':                     c.dehoist,
            'delete-comments':              c.deleteComments,
            'delete-node':                  c.deleteOutline,
            'demote':                       c.demote,
            'dump-outline':                 c.dumpOutline,
            'edit-headline':                c.editHeadline,
            'end-edit-headline':            f.endEditLabelCommand,
            'equal-sized-panes':            f.equalSizedPanes,
            'execute-script':               c.executeScript,
            'exit-leo':                     g.app.onQuit,
            'expand-all':                   c.expandAllHeadlines,
            'expand-ancestors-only':        c.expandOnlyAncestorsOfNode,
            'expand-and-go-right':          c.expandNodeAndGoToFirstChild,
            'expand-next-level':            c.expandNextLevel,
            'expand-node':                  c.expandNode,
            'expand-or-go-right':           c.expandNodeOrGoToFirstChild,
            'expand-prev-level':            c.expandPrevLevel,
            'expand-to-level-1':            c.expandLevel1,
            'expand-to-level-2':            c.expandLevel2,
            'expand-to-level-3':            c.expandLevel3,
            'expand-to-level-4':            c.expandLevel4,
            'expand-to-level-5':            c.expandLevel5,
            'expand-to-level-6':            c.expandLevel6,
            'expand-to-level-7':            c.expandLevel7,
            'expand-to-level-8':            c.expandLevel8,
            'expand-to-level-9':            c.expandLevel9,
            'export-headlines':             c.exportHeadlines,
            'extract':                      c.extract,
            'extract-names':                c.extractSectionNames,
            'extract-section':              c.extractSection,
            'find-next-clone':              c.findNextClone,
            'flatten-outline':              c.flattenOutline,
            'go-back':                      c.goPrevVisitedNode,
            'go-forward':                   c.goNextVisitedNode,
            'goto-first-node':              c.goToFirstNode,
            'goto-first-sibling':           c.goToFirstSibling,
            'goto-first-visible-node':      c.goToFirstVisibleNode,
            'goto-last-node':               c.goToLastNode,
            'goto-last-sibling':            c.goToLastSibling,
            'goto-last-visible-node':       c.goToLastVisibleNode,
            'goto-next-changed':            c.goToNextDirtyHeadline,
            'goto-next-clone':              c.goToNextClone,
            'goto-next-marked':             c.goToNextMarkedHeadline,
            'goto-next-node':               c.selectThreadNext,
            'goto-next-sibling':            c.goToNextSibling,
            'goto-next-visible':            c.selectVisNext,
            'goto-parent':                  c.goToParent,
            'goto-prev-node':               c.selectThreadBack,
            'goto-prev-sibling':            c.goToPrevSibling,
            'goto-prev-visible':            c.selectVisBack,
            'hide-invisibles':              c.hideInvisibles,
            'hoist':                        c.hoist,
            'import-at-file':               c.importAtFile,
            'import-at-root':               c.importAtRoot,
            'import-cweb-files':            c.importCWEBFiles,
            'import-derived-file':          c.importDerivedFile,
            'import-flattened-outline':     c.importFlattenedOutline,
            'import-noweb-files':           c.importNowebFiles,
            'indent-region':                c.indentBody,
            'insert-body-time':             c.insertBodyTime,
            'insert-child':                 c.insertChild,
            'insert-headline-time':         f.insertHeadlineTime,
            'insert-node':                  c.insertHeadline,
            'mark':                         c.markHeadline,
            'mark-changed-items':           c.markChangedHeadlines,
            'mark-changed-roots':           c.markChangedRoots,
            'mark-clones':                  c.markClones,
            'mark-subheads':                c.markSubheads,
            'match-brackets':               c.findMatchingBracket,
            'minimize-all':                 f.minimizeAll,
            'move-outline-down':            c.moveOutlineDown,
            'move-outline-left':            c.moveOutlineLeft,
            'move-outline-right':           c.moveOutlineRight,
            'move-outline-up':              c.moveOutlineUp,
            'new':                          c.new,
            'open-compare-window':          c.openCompareWindow,
            'open-find-dialog':             c.showFindPanel, # Deprecated.
            'open-leoDocs-leo':             c.leoDocumentation,
            'open-leoPlugins-leo':          c.openLeoPlugins,
            'open-leoSettings-leo':         c.openLeoSettings,
            'open-myLeoSettings-leo':       c.openMyLeoSettings,
            'open-offline-tutorial':        f.leoHelp,
            'open-online-home':             c.leoHome,
            'open-online-tutorial':         c.leoTutorial,
            'open-outline':                 c.open,
            'open-python-window':           c.openPythonWindow,
            'open-quickstart-leo':          c.leoQuickStart,
            'open-scripts-leo':             c.openLeoScripts,
            'open-users-guide':             c.leoUsersGuide,
            'open-with':                    c.openWith,
            'outline-to-cweb':              c.outlineToCWEB,
            'outline-to-noweb':             c.outlineToNoweb,
            'paste-node':                   c.pasteOutline,
            'paste-retaining-clones':       c.pasteOutlineRetainingClones,
            'paste-text':                   f.pasteText,
            'pretty-print-all-python-code': c.prettyPrintAllPythonCode,
            'pretty-print-python-code':     c.prettyPrintPythonCode,
            'promote':                      c.promote,
            'read-at-auto-nodes':           c.readAtAutoNodes,
            'read-at-file-nodes':           c.readAtFileNodes,
            'read-at-shadow-nodes':         c.readAtShadowNodes,
            'read-file-into-node':          c.readFileIntoNode,
            'read-outline-only':            c.readOutlineOnly,
            'redo':                         c.undoer.redo,
            'reformat-paragraph':           c.reformatParagraph,
            'remove-sentinels':             c.removeSentinels,
            'resize-to-screen':             f.resizeToScreen,
            'revert':                       c.revert,
            'save-file':                    c.save,
            'save-file-as':                 c.saveAs,
            'save-file-as-unzipped':        c.saveAsUnzipped,
            'save-file-as-zipped':          c.saveAsZipped,
            'save-file-to':                 c.saveTo,
            'set-colors':                   c.colorPanel,
            'set-font':                     c.fontPanel,
            'settings':                     c.preferences,
            'show-invisibles':              c.showInvisibles,
            'sort-children':                c.sortChildren,
            'sort-recent-files':            c.sortRecentFiles,
            'sort-siblings':                c.sortSiblings,
            'tangle':                       c.tangle,
            'tangle-all':                   c.tangleAll,
            'tangle-marked':                c.tangleMarked,
            'toggle-active-pane':           f.toggleActivePane,
            'toggle-angle-brackets':        c.toggleAngleBrackets,
            'toggle-invisibles':            c.toggleShowInvisibles,
            'toggle-sparse-move':           c.toggleSparseMove,
            'toggle-split-direction':       f.toggleSplitDirection,
            'undo':                         c.undoer.undo,
            'unindent-region':              c.dedentBody,
            'unmark-all':                   c.unmarkAll,
            'untangle':                     c.untangle,
            'untangle-all':                 c.untangleAll,
            'untangle-marked':              c.untangleMarked,
            'weave':                        c.weave,
            'write-at-auto-nodes':          c.atFileCommands.writeAtAutoNodes,
            'write-at-file-nodes':          c.fileCommands.writeAtFileNodes,
            'write-at-shadow-nodes':        c.fileCommands.writeAtShadowNodes,
            'write-dirty-at-auto-nodes':    c.atFileCommands.writeDirtyAtAutoNodes,
            'write-dirty-at-file-nodes':    c.fileCommands.writeDirtyAtFileNodes,
            'write-dirty-at-shadow-nodes':  c.fileCommands.writeDirtyAtShadowNodes,
            'write-file-from-node':         c.writeFileFromNode,
            'write-missing-at-file-nodes':  c.fileCommands.writeMissingAtFileNodes,
            'write-outline-only':           c.fileCommands.writeOutlineOnly,
        }
        #@-node:ekr.20050920084036.189:<< define dictionary d of names and Leo commands >>
        #@nl

        # Create a callback for each item in d.
        for name in sorted(d):
            f = d.get(name)
            d2 [name] = f
            k.inverseCommandsDict [f.__name__] = name
            # g.trace('leoCommands %24s = %s' % (f.__name__,name))

        return d2
    #@-node:ekr.20050920084036.188:leoCommands.getPublicCommands
    #@-others
#@-node:ekr.20050920084036.186:leoCommandsClass (add docstrings)
#@+node:ekr.20050920084036.190:macroCommandsClass
class macroCommandsClass (baseEditCommandsClass):

    '''Define the following commands:

    call-kbd-macro
    call-last-kbd-macro
    load-kbd-macros
    name-last-kbd-macro
    print-macros
    save-kbd-macros
    start-kbd-macro
    '''

    #@    @+others
    #@+node:ekr.20050920084036.191: ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.lastMacro = None
        self.macros = []
        self.macro = []
        self.namedMacros = {}

        # Important: we must not interfere with k.state in startKbdMacro!
        self.recordingMacro = False
    #@-node:ekr.20050920084036.191: ctor
    #@+node:ekr.20050920084036.192: getPublicCommands
    def getPublicCommands (self):

        return {
            'call-last-kbd-macro':  self.callLastKeyboardMacro,
            'call-kbd-macro':       self.callNamedMacro,
            'print-macros':         self.printMacros,
            'name-last-kbd-macro':  self.nameLastKbdMacro,
            'load-kbd-macros':      self.loadFile,
            'save-kbd-macros':      self.saveMacros,
            'start-kbd-macro':      self.startKbdMacro,
        }
    #@-node:ekr.20050920084036.192: getPublicCommands
    #@+node:ekr.20050920085536.15:addToDoAltX (common helper)
    # Called from loadFile and nameLastKbdMacro.

    def addToDoAltX (self,name,macro):

        '''Adds macro to Alt-X commands.'''

        k= self ; c = k.c

        g.trace(name,macro)

        if name in c.commandsDict:
            return False

        def func (event,macro=macro):
            return self.executeMacro(macro)

        c.commandsDict [name] = func
        self.namedMacros [name] = macro
        return True
    #@-node:ekr.20050920085536.15:addToDoAltX (common helper)
    #@+node:ekr.20050920084036.202:callLastKeyboardMacro
    # Called from universal-command.

    def callLastKeyboardMacro (self,event):

        '''Call the last recorded keyboard macro.'''

        if self.lastMacro:
            self.executeMacro(self.lastMacro)
    #@nonl
    #@-node:ekr.20050920084036.202:callLastKeyboardMacro
    #@+node:ekr.20050920084036.194:callNamedMacro
    def callNamedMacro (self,event):

        '''Prompts for a macro name to save, then executes it.'''

        k = self.k ; tag = 'macro-name'
        state = k.getState(tag)
        prompt = 'Call macro named: '

        if state == 0:
            k.setLabelBlue(prompt,protect=True)
            k.getArg(event,tag,1,self.callNamedMacro)
        else:
            macro = self.namedMacros.get(k.arg)
            # Must do this first!
            k.clearState()
            if macro:
                self.executeMacro(macro)
            else:
                g.es('no macro named %s' % k.arg)
            k.resetLabel()

    #@-node:ekr.20050920084036.194:callNamedMacro
    #@+node:ekr.20050920084036.206:endKbdMacro
    def endKbdMacro (self,event=None):

        '''Stop recording a keyboard macro.'''

        k = self.k
        self.recordingMacro = False
            # Tell k.masterKeyHandler and masterCommandHandler we are done.

        if self.macro:
            # self.macro = self.macro [: -4]
            self.macros.insert(0,self.macro)
            self.lastMacro = self.macro[:]
            self.macro = []
            k.setLabelBlue('Keyboard macro defined, not named')
        else:
            k.setLabelBlue('Empty keyboard macro')
    #@-node:ekr.20050920084036.206:endKbdMacro
    #@+node:ekr.20050920084036.203:executeMacro
    def executeMacro (self,macro):

        c = self.c ; k = self.k

        c.bodyWantsFocusNow()

        for event in macro:
            # New in Leo 4.6: macro entries are leoKeyEvents.
            g.trace(event.stroke)
            k.masterKeyHandler(event,stroke=event.stroke)
    #@-node:ekr.20050920084036.203:executeMacro
    #@+node:ekr.20050920084036.196:loadFile & helper
    def loadFile (self,event):

        '''Asks for a macro file name to load.'''

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

        if not fileName: return

        try:
            f = open(fileName)
            self.loadMacros(f)
        except IOError:
            g.es('can not open',fileName)
    #@+node:ekr.20050920084036.197:loadMacros
    def loadMacros (self,f):

        '''Loads a macro file into the macros dictionary.'''

        c = self.c ; w = c.frame.body.bodyCtrl
        try:
            d = pickle.load(f)
        except pickle.UnpicklingError:
            g.es('error unpickling %s' % f.name)
            return

        # g.trace(f.name,d)

        for name in d:
            aList = d.get(name)
            macro = []
            for stroke in aList:
                # Create a dummy event with just enough attribute
                # to keep k.masterKeyHandler happy
                actualEvent = g.Bunch(stroke=stroke,char=stroke,widget=w)
                event = g.app.gui.leoKeyEvent(actualEvent,c)
                macro.append(event)
            self.addToDoAltX(name,macro)
                # sets self.namedMacros[name]=macro


    #@-node:ekr.20050920084036.197:loadMacros
    #@-node:ekr.20050920084036.196:loadFile & helper
    #@+node:ekr.20050920084036.198:nameLastKbdMacro
    def nameLastKbdMacro (self,event):

        '''Prompt for the name to be given to the last recorded macro.'''

        k = self.k ; state = k.getState('name-macro')

        if state == 0:
            k.setLabelBlue('Name of macro: ',protect=True)
            k.getArg(event,'name-macro',1,self.nameLastKbdMacro)
        else:
            k.clearState()
            name = k.arg
            self.addToDoAltX(name,self.lastMacro)
            k.setLabelGrey('Macro defined: %s' % name)
    #@-node:ekr.20050920084036.198:nameLastKbdMacro
    #@+node:ekr.20090201152408.1:printMacros
    def printMacros (self,event=None):

        names = [z for z in self.namedMacros]
        g.es(''.join(names),tabName='Macros')
    #@-node:ekr.20090201152408.1:printMacros
    #@+node:ekr.20050920084036.199:saveMacros & helpers
    def saveMacros (self,event=None):

        '''Asks for a file name and saves it.'''

        fileName = g.app.gui.runSaveFileDialog(
            initialfile = None,
            title='Save Macros',
            filetypes = [("Text","*.txt"), ("All files","*")],
            defaultextension = ".txt")

        if not fileName: return

        try:
            f = open(fileName,'a+')
            f.seek(0)
            if f:
                self.saveMacrosHelper(f)
        except IOError:
            g.es('can not create',fileName)

    #@+node:ekr.20050920084036.200:saveMacrosHelper
    def saveMacrosHelper( self,f):

        '''Saves all named macros.'''

        # fname = f.name
        # try:
            # macros = pickle.load( f )
        # except Exception:
            # macros = {}
        # f.close()

        d = {}
        for name in self.namedMacros:
            macro = self.namedMacros.get(name)
            # Just save the essential part of the event.
            # It must be picklable.
            aList = [event.stroke for event in macro]
            g.trace(name,aList)
            d[name] = aList
            # f = open( fname, 'w' )
            pickle.dump(d, f )
            f.close()
    #@-node:ekr.20050920084036.200:saveMacrosHelper
    #@-node:ekr.20050920084036.199:saveMacros & helpers
    #@+node:ekr.20050920084036.204:startKbdMacro
    def startKbdMacro (self,event):

        '''Start recording a keyboard macro.'''

        k = self.k

        if not self.recordingMacro:
            self.recordingMacro = True
                # A flag for k.masterCommandHandler & k.masterKeyHandler.
            k.setLabelBlue('Recording macro. ctrl-g to end...',protect=True)
        else:
            g.trace(event)
            self.macro.append(event)
    #@-node:ekr.20050920084036.204:startKbdMacro
    #@-others
#@nonl
#@-node:ekr.20050920084036.190:macroCommandsClass
#@+node:ekr.20050920084036.221:rectangleCommandsClass
class rectangleCommandsClass (baseEditCommandsClass):

    #@    @+others
    #@+node:ekr.20050920084036.222: ctor & finishCreate
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.theKillRectangle = [] # Do not re-init this!
        self.stringRect = None

    def finishCreate(self):

        baseEditCommandsClass.finishCreate(self)

        self.commandsDict = {
            'c': ('clear-rectangle',    self.clearRectangle),
            'd': ('delete-rectangle',   self.deleteRectangle),
            'k': ('kill-rectangle',     self.killRectangle),
            'o': ('open-rectangle',     self.openRectangle),
            'r': ('copy-rectangle-to-register',
                self.c.registerCommands.copyRectangleToRegister),
            't': ('string-rectangle',   self.stringRectangle),
            'y': ('yank-rectangle',     self.yankRectangle),
        }
    #@-node:ekr.20050920084036.222: ctor & finishCreate
    #@+node:ekr.20051004112630:check
    def check (self,event,warning='No rectangle selected'):

        '''Return True if there is a selection.
        Otherwise, return False and issue a warning.'''

        return self._chckSel(event,warning)
    #@-node:ekr.20051004112630:check
    #@+node:ekr.20050920084036.223:getPublicCommands
    def getPublicCommands (self):

        return {
            'clear-rectangle':  self.clearRectangle,
            'close-rectangle':  self.closeRectangle,
            'delete-rectangle': self.deleteRectangle,
            'kill-rectangle':   self.killRectangle,
            'open-rectangle':   self.openRectangle,
            'string-rectangle': self.stringRectangle,
            'yank-rectangle':   self.yankRectangle,
        }
    #@-node:ekr.20050920084036.223:getPublicCommands
    #@+node:ekr.20051215103053:beginCommand & beginCommandWithEvent (rectangle)
    def beginCommand (self,undoType='Typing'):

        w = baseEditCommandsClass.beginCommand(self,undoType)
        r1,r2,r3,r4 = self.getRectanglePoints(w)
        return w,r1,r2,r3,r4


    def beginCommandWithEvent (self,event,undoType='Typing'):

        '''Do the common processing at the start of each command.'''

        w = baseEditCommandsClass.beginCommandWithEvent(self,event,undoType)
        r1,r2,r3,r4 = self.getRectanglePoints(w)
        return w,r1,r2,r3,r4
    #@-node:ekr.20051215103053:beginCommand & beginCommandWithEvent (rectangle)
    #@+node:ekr.20050920084036.224:Entries (rectangleCommandsClass)
    #@+node:ekr.20050920084036.225:clearRectangle
    def clearRectangle (self,event):

        '''Clear the rectangle defined by the start and end of selected text.'''

        w = self.editWidget(event)
        if not w or not self.check(event): return

        w,r1,r2,r3,r4 = self.beginCommand('clear-rectangle')

        # Change the text.
        fill = ' ' *(r4-r2)
        for r in range(r1,r3+1):
            w.delete('%s.%s' % (r,r2),'%s.%s' % (r,r4))
            w.insert('%s.%s' % (r,r2),fill)

        w.setSelectionRange('%s.%s'%(r1,r2),'%s.%s'%(r3,r2+len(fill)))

        self.endCommand()
    #@-node:ekr.20050920084036.225:clearRectangle
    #@+node:ekr.20050920084036.226:closeRectangle
    def closeRectangle (self,event):

        '''Delete the rectangle if it contains nothing but whitespace..'''

        w = self.editWidget(event)
        if not w or not self.check(event): return

        w,r1,r2,r3,r4 = self.beginCommand('close-rectangle')

        # Return if any part of the selection contains something other than whitespace.
        for r in range(r1,r3+1):
            s = w.get('%s.%s' % (r,r2),'%s.%s' % (r,r4))
            if s.strip(): return

        # Change the text.
        for r in range(r1,r3+1):
            w.delete('%s.%s' % (r,r2),'%s.%s' % (r,r4))

        i = '%s.%s' % (r1,r2)
        j = '%s.%s' % (r3,r2)
        w.setSelectionRange(i,j,insert=j)

        self.endCommand()
    #@-node:ekr.20050920084036.226:closeRectangle
    #@+node:ekr.20050920084036.227:deleteRectangle
    def deleteRectangle (self,event):

        '''Delete the rectangle defined by the start and end of selected text.'''

        w = self.editWidget(event)
        if not w or not self.check(event): return

        w,r1,r2,r3,r4 = self.beginCommand('delete-rectangle')

        for r in range(r1,r3+1):
            w.delete('%s.%s' % (r,r2),'%s.%s' % (r,r4))

        i = '%s.%s' % (r1,r2)
        j = '%s.%s' % (r3,r2)
        w.setSelectionRange(i,j,insert=j)

        self.endCommand()
    #@-node:ekr.20050920084036.227:deleteRectangle
    #@+node:ekr.20050920084036.228:killRectangle
    def killRectangle (self,event):

        '''Kill the rectangle defined by the start and end of selected text.'''

        w = self.editWidget(event)
        if not w or not self.check(event): return

        w,r1,r2,r3,r4 = self.beginCommand('kill-rectangle')

        self.theKillRectangle = []

        for r in range(r1,r3+1):
            s = w.get('%s.%s' % (r,r2),'%s.%s' % (r,r4))
            self.theKillRectangle.append(s)
            w.delete('%s.%s' % (r,r2),'%s.%s' % (r,r4))

        # g.trace('killRect',repr(self.theKillRectangle))

        if self.theKillRectangle:
            ins = '%s.%s' % (r,r2)
            w.setSelectionRange(ins,ins,insert=ins)

        self.endCommand()
    #@-node:ekr.20050920084036.228:killRectangle
    #@+node:ekr.20050920084036.230:openRectangle
    def openRectangle (self,event):

        '''Insert blanks in the rectangle defined by the start and end of selected text.
        This pushes the previous contents of the rectangle rightward.'''

        w = self.editWidget(event)
        if not w or not self.check(event): return

        w,r1,r2,r3,r4 = self.beginCommand('open-rectangle')

        fill = ' ' * (r4-r2)
        for r in range(r1,r3+1):
            w.insert('%s.%s' % (r,r2),fill)

        i = '%s.%s' % (r1,r2)
        j = '%s.%s' % (r3,r2+len(fill))
        w.setSelectionRange(i,j,insert=j)

        self.endCommand()
    #@-node:ekr.20050920084036.230:openRectangle
    #@+node:ekr.20050920084036.232:stringRectangle
    def stringRectangle (self,event):

        '''Prompt for a string, then replace the contents of a rectangle
        with a string on each line.'''

        c = self.c ; k = self.k ; state = k.getState('string-rect')
        if g.app.unitTesting:
            state = 1 ; k.arg = 's...s' # This string is known to the unit test.
            w = self.editWidget(event)
            self.stringRect = self.getRectanglePoints(w)
        if state == 0:
            w = self.editWidget(event) # sets self.w
            if not w or not self.check(event): return
            self.stringRect = self.getRectanglePoints(w)
            k.setLabelBlue('String rectangle: ',protect=True)
            k.getArg(event,'string-rect',1,self.stringRectangle)
        else:
            k.clearState()
            k.resetLabel()
            c.bodyWantsFocus()
            w = self.w
            self.beginCommand('string-rectangle')
            r1, r2, r3, r4 = self.stringRect
            s = w.getAllText()
            for r in range(r1,r3+1):
                i = g.convertRowColToPythonIndex(s,r-1,r2)
                j = g.convertRowColToPythonIndex(s,r-1,r4)
                s = s[:i] + k.arg + s[j:]
            w.setAllText(s)
            i = g.convertRowColToPythonIndex(s,r1-1,r2)
            j = g.convertRowColToPythonIndex(s,r3-1,r2+len(k.arg))
            w.setSelectionRange(i,j)
            self.endCommand()
            # 2010/1/1: Fix bug 480422:
            # string-rectangle kills syntax highlighting.
            c.frame.body.recolor(c.p,incremental=False)

    #@-node:ekr.20050920084036.232:stringRectangle
    #@+node:ekr.20050920084036.229:yankRectangle
    def yankRectangle (self,event,killRect=None):

        '''Yank into the rectangle defined by the start and end of selected text.'''

        c = self.c ; k = self.k
        w = self.editWidget(event)
        if not w: return

        killRect = killRect or self.theKillRectangle
        if g.app.unitTesting:
            # This value is used by the unit test.
            killRect = ['Y1Y','Y2Y','Y3Y','Y4Y']
        elif not killRect:
            k.setLabelGrey('No kill rect') ; return

        w,r1,r2,r3,r4 = self.beginCommand('yank-rectangle')

        n = 0
        for r in range(r1,r3+1):
            # g.trace(n,r,killRect[n])
            if n >= len(killRect): break
            w.delete('%s.%s' % (r,r2), '%s.%s' % (r,r4))
            w.insert('%s.%s' % (r,r2), killRect[n])
            n += 1

        i = '%s.%s' % (r1,r2)
        j = '%s.%s' % (r3,r2+len(killRect[n-1]))
        w.setSelectionRange(i,j,insert=j)

        self.endCommand()
    #@-node:ekr.20050920084036.229:yankRectangle
    #@-node:ekr.20050920084036.224:Entries (rectangleCommandsClass)
    #@-others
#@-node:ekr.20050920084036.221:rectangleCommandsClass
#@+node:ekr.20050920084036.234:registerCommandsClass
class registerCommandsClass (baseEditCommandsClass):

    '''A class to represent registers a-z and the corresponding Emacs commands.'''

    #@    @+others
    #@+node:ekr.20051004095209:Birth
    #@+node:ekr.20050920084036.235: ctor, finishCreate & init
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.methodDict, self.helpDict = self.addRegisterItems()
        self.init()

    def finishCreate (self):

        baseEditCommandsClass.finishCreate(self) # finish the base class.

        if self.k.useGlobalRegisters:
            self.registers = leoKeys.keyHandlerClass.global_registers
        else:
            self.registers = {}

    def init (self):

        self.method = None 
        self.registerMode = 0 # Must be an int.
    #@-node:ekr.20050920084036.235: ctor, finishCreate & init
    #@+node:ekr.20050920084036.247: getPublicCommands
    def getPublicCommands (self):

        return {
            'append-to-register':           self.appendToRegister,
            'copy-rectangle-to-register':   self.copyRectangleToRegister,
            'copy-to-register':             self.copyToRegister,
            'increment-register':           self.incrementRegister,
            'insert-register':              self.insertRegister,
            'jump-to-register':             self.jumpToRegister,
            # 'number-to-register':           self.numberToRegister,
            'point-to-register':            self.pointToRegister,
            'prepend-to-register':          self.prependToRegister,
            'view-register':                self.viewRegister,
        }
    #@-node:ekr.20050920084036.247: getPublicCommands
    #@+node:ekr.20050920084036.252:addRegisterItems
    def addRegisterItems( self ):

        methodDict = {
            '+':        self.incrementRegister,
            ' ':        self.pointToRegister,
            'a':        self.appendToRegister,
            'i':        self.insertRegister,
            'j':        self.jumpToRegister,
            # 'n':        self.numberToRegister,
            'p':        self.prependToRegister,
            'r':        self.copyRectangleToRegister,
            's':        self.copyToRegister,
            'v' :       self.viewRegister,
        }    

        helpDict = {
            's':    'copy to register',
            'i':    'insert from register',
            '+':    'increment register',
            'n':    'number to register',
            'p':    'prepend to register',
            'a':    'append to register',
            ' ':    'point to register',
            'j':    'jump to register',
            'r':    'rectangle to register',
            'v':    'view register',
        }

        return methodDict, helpDict
    #@-node:ekr.20050920084036.252:addRegisterItems
    #@-node:ekr.20051004095209:Birth
    #@+node:ekr.20051004123217:checkBodySelection
    def checkBodySelection (self,warning='No text selected'):

        return self._chckSel(event=None,warning=warning)
    #@-node:ekr.20051004123217:checkBodySelection
    #@+node:ekr.20050920084036.236:Entries... (register commands)
    #@+node:ekr.20050920084036.238:appendToRegister
    def appendToRegister (self,event):

        '''Prompt for a register name and append the selected text to the register's contents.'''

        c = self.c ; k = self.k ; state = k.getState('append-to-reg')

        if state == 0:
            k.setLabelBlue('Append to register: ',protect=True)
            k.setState('append-to-reg',1,self.appendToRegister)
        else:
            k.clearState()
            if self.checkBodySelection():
                if event.keysym.isalpha():
                    w = c.frame.body.bodyCtrl
                    c.bodyWantsFocus()
                    key = event.keysym.lower()
                    val = self.registers.get(key,'')
                    try:
                        val = val + w.get('sel.first','sel.last') ###
                    except Exception:
                        pass
                    self.registers[key] = val
                    k.setLabelGrey('Register %s = %s' % (key,repr(val)))
                else:
                    k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.238:appendToRegister
    #@+node:ekr.20050920084036.237:prependToRegister
    def prependToRegister (self,event):

        '''Prompt for a register name and prepend the selected text to the register's contents.'''

        c = self.c ; k = self.k ; state = k.getState('prepend-to-reg')

        if state == 0:
            k.setLabelBlue('Prepend to register: ',protect=True)
            k.setState('prepend-to-reg',1,self.prependToRegister)
        else:
            k.clearState()
            if self.checkBodySelection():
                if event.keysym.isalpha():
                    w = c.frame.body.bodyCtrl
                    c.bodyWantsFocus()
                    key = event.keysym.lower()
                    val = self.registers.get(key,'')
                    try:
                        val = w.get('sel.first','sel.last') + val ###
                    except Exception:
                        pass
                    self.registers[key] = val
                    k.setLabelGrey('Register %s = %s' % (key,repr(val)))
                else:
                    k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.237:prependToRegister
    #@+node:ekr.20050920084036.239:copyRectangleToRegister
    def copyRectangleToRegister (self,event):

        '''Prompt for a register name and append the rectangle defined by selected
        text to the register's contents.'''

        c = self.c ; k = self.k ; state = k.getState('copy-rect-to-reg')

        if state == 0:
            w = self.editWidget(event) # sets self.w
            if not w: return
            k.commandName = 'copy-rectangle-to-register'
            k.setLabelBlue('Copy Rectangle To Register: ',protect=True)
            k.setState('copy-rect-to-reg',1,self.copyRectangleToRegister)
        elif self.checkBodySelection('No rectangle selected'):
            k.clearState()
            if event.keysym.isalpha():
                key = event.keysym.lower()
                w = self.w
                c.widgetWantsFocusNow(w)
                r1, r2, r3, r4 = self.getRectanglePoints(w)
                rect = []
                while r1 <= r3:
                    txt = w.get('%s.%s' % (r1,r2),'%s.%s' % (r1,r4))
                    rect.append(txt)
                    r1 = r1 + 1
                self.registers [key] = rect
                k.setLabelGrey('Register %s = %s' % (key,repr(rect)))
            else:
                k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.239:copyRectangleToRegister
    #@+node:ekr.20050920084036.240:copyToRegister
    def copyToRegister (self,event):

        '''Prompt for a register name and append the selected text to the register's contents.'''

        c = self.c ; k = self.k ; state = k.getState('copy-to-reg')

        if state == 0:
            k.commandName = 'copy-to-register'
            k.setLabelBlue('Copy to register: ',protect=True)
            k.setState('copy-to-reg',1,self.copyToRegister)
        else:
            k.clearState()
            if self.checkBodySelection():
                if event.keysym.isalpha():
                    key = event.keysym.lower()
                    w = c.frame.body.bodyCtrl
                    c.bodyWantsFocus()
                    try:
                        val = w.get('sel.first','sel.last') ###
                    except Exception:
                        g.es_exception()
                        val = ''
                    self.registers[key] = val
                    k.setLabelGrey('Register %s = %s' % (key,repr(val)))
                else:
                    k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.240:copyToRegister
    #@+node:ekr.20050920084036.241:incrementRegister
    def incrementRegister (self,event):

        '''Prompt for a register name and increment its value if it has a numeric value.'''

        c = self.c ; k = self.k ; state = k.getState('increment-reg')

        if state == 0:
            k.setLabelBlue('Increment register: ',protect=True)
            k.setState('increment-reg',1,self.incrementRegister)
        else:
            k.clearState()
            if self._checkIfRectangle(event):
                pass # Error message is in the label.
            elif event.keysym.isalpha():
                key = event.keysym.lower()
                val = self.registers.get(key,0)
                try:
                    val = str(int(val)+1)
                    self.registers[key] = val
                    k.setLabelGrey('Register %s = %s' % (key,repr(val)))
                except ValueError:
                    k.setLabelGrey("Can't increment register %s = %s" % (key,val))
            else:
                k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.241:incrementRegister
    #@+node:ekr.20050920084036.242:insertRegister
    def insertRegister (self,event):

        '''Prompt for a register name and and insert the value of another register into its contents.'''

        c = self.c ; k = self.k ; state = k.getState('insert-reg')

        if state == 0:
            k.commandName = 'insert-register'
            k.setLabelBlue('Insert register: ',protect=True)
            k.setState('insert-reg',1,self.insertRegister)
        else:
            k.clearState()
            if event.keysym.isalpha():
                w = c.frame.body.bodyCtrl
                c.bodyWantsFocus()
                key = event.keysym.lower()
                val = self.registers.get(key)
                if val:
                    if type(val)==type([]):
                        c.rectangleCommands.yankRectangle(val)
                    else:
                        i = w.getInsertPoint()
                        w.insert(i,val)
                    k.setLabelGrey('Inserted register %s' % key)
                else:
                    k.setLabelGrey('Register %s is empty' % key)
            else:
                k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.242:insertRegister
    #@+node:ekr.20050920084036.243:jumpToRegister
    def jumpToRegister (self,event):

        '''Prompt for a register name and set the insert point to the value in its register.'''

        c = self.c ; k = self.k ; state = k.getState('jump-to-reg')

        if state == 0:
            k.setLabelBlue('Jump to register: ',protect=True)
            k.setState('jump-to-reg',1,self.jumpToRegister)
        else:
            k.clearState()
            if event.keysym.isalpha():
                if self._checkIfRectangle(event): return
                key = event.keysym.lower()
                val = self.registers.get(key)
                w = c.frame.body.bodyCtrl
                c.bodyWantsFocus()
                if val:
                    try:
                        w.setInsertPoint(val)
                        k.setLabelGrey('At %s' % repr(val))
                    except Exception:
                        k.setLabelGrey('Register %s is not a valid location' % key)
                else:
                    k.setLabelGrey('Register %s is empty' % key)
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.243:jumpToRegister
    #@+node:ekr.20050920084036.244:numberToRegister (not used)
    #@+at
    # C-u number C-x r n reg
    #     Store number into register reg (number-to-register).
    # C-u number C-x r + reg
    #     Increment the number in register reg by number 
    # (increment-register).
    # C-x r g reg
    #     Insert the number from register reg into the buffer.
    #@-at
    #@@c

    def numberToRegister (self,event):

        k = self.k ; state = k.getState('number-to-reg')

        if state == 0:
            k.commandName = 'number-to-register'
            k.setLabelBlue('Number to register: ',protect=True)
            k.setState('number-to-reg',1,self.numberToRegister)
        else:
            k.clearState()
            if event.keysym.isalpha():
                # self.registers[event.keysym.lower()] = str(0)
                k.setLabelGrey('number-to-register not ready yet.')
            else:
                k.setLabelGrey('Register must be a letter')
    #@-node:ekr.20050920084036.244:numberToRegister (not used)
    #@+node:ekr.20050920084036.245:pointToRegister
    def pointToRegister (self,event):

        '''Prompt for a register name and put a value indicating the insert point in the register.'''

        c = self.c ; k = self.k ; state = k.getState('point-to-reg')

        if state == 0:
            k.commandName = 'point-to-register'
            k.setLabelBlue('Point to register: ',protect=True)
            k.setState('point-to-reg',1,self.pointToRegister)
        else:
            k.clearState()
            if event.keysym.isalpha():
                w = c.frame.body.bodyCtrl
                c.bodyWantsFocus()
                key = event.keysym.lower()
                val = w.getInsertPoint()
                self.registers[key] = val
                k.setLabelGrey('Register %s = %s' % (key,repr(val)))
            else:
                k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.245:pointToRegister
    #@+node:ekr.20050920084036.246:viewRegister
    def viewRegister (self,event):

        '''Prompt for a register name and print its contents.'''

        c = self.c ; k = self.k ; state = k.getState('view-reg')

        if state == 0:
            k.commandName = 'view-register'
            k.setLabelBlue('View register: ',protect=True)
            k.setState('view-reg',1,self.viewRegister)
        else:
            k.clearState()
            if event.keysym.isalpha():
                key = event.keysym.lower()
                val = self.registers.get(key)
                k.setLabelGrey('Register %s = %s' % (key,repr(val)))
            else:
                k.setLabelGrey('Register must be a letter')
        c.bodyWantsFocus()
    #@-node:ekr.20050920084036.246:viewRegister
    #@-node:ekr.20050920084036.236:Entries... (register commands)
    #@-others
#@-node:ekr.20050920084036.234:registerCommandsClass
#@+node:ekr.20051023094009:Search classes
#@+node:ekr.20060123125256:class minibufferFind (the findHandler)
class minibufferFind (baseEditCommandsClass):

    '''An adapter class that implements minibuffer find commands using the (hidden) Find Tab.'''

    #@    @+others
    #@+node:ekr.20060123125317.2: ctor (minibufferFind)
    def __init__(self,c,finder):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        # g.trace('minibufferFind: finder',finder)

        self.c = c
        self.k = k = c.k
        self.w = None
        self.finder = finder
        self.findTextList = []
        self.changeTextList = []

        commandName = 'replace-string'
        s = k.getShortcutForCommandName(commandName)
        s = k.prettyPrintKey(s)
        s = k.shortcutFromSetting(s)
        # g.trace('replaceStringShortcut',s)
        self.replaceStringShortcut = s
    #@-node:ekr.20060123125317.2: ctor (minibufferFind)
    #@+node:ekr.20090126063121.1:editWidget (minibufferFind)
    def editWidget (self,event):

        '''An override of baseEditCommands.editWidget

        that does *not* set focus when using anything other than the tk gui.

        This prevents this class from cachinganeditwidget import 
        that is about to be deallocated.'''

        c = self.c ; w = event and event.widget
        bodyCtrl = self.c.frame.body and self.c.frame.body.bodyCtrl

        # g.trace('isText',g.app.gui.isTextWidget(w),'w',w,g.app.gui.widget_name(w))

        if g.app.gui.guiName() == 'tkinter':

            # New in Leo 4.5: single-line editing commands apply to minibuffer widget.
            if w and g.app.gui.isTextWidget(w):
                self.w = w
            else:
                self.w = bodyCtrl
            if self.w:
                c.widgetWantsFocusNow(self.w)
        else:
            # Do not cache a pointer to a headline!
            # It will die when the minibuffer is selected.
            self.w = bodyCtrl

        return self.w
    #@-node:ekr.20090126063121.1:editWidget (minibufferFind)
    #@+node:ekr.20060124140114: Options (minibufferFind)
    #@+node:ekr.20060124123133:setFindScope
    def setFindScope(self,where):

        '''Set the find-scope radio buttons.

        `where` must be in ('node-only','entire-outline','suboutline-only'). '''

        h = self.finder

        if where in ('node-only','entire-outline','suboutline-only'):
            var = h.svarDict['radio-search-scope'].get()
            if var:
                h.svarDict["radio-search-scope"].set(where)
        else:
            g.trace('oops: bad `where` value: %s' % where)
    #@-node:ekr.20060124123133:setFindScope
    #@+node:ekr.20060124122844:get/set/toggleOption (minibufferFind)
    # This redirection is required to remove gui-dependencies.

    def getOption (self,ivar):          return self.finder.getOption(ivar)
    def setOption (self,ivar,val):      self.finder.setOption(ivar,val)
    def toggleOption (self,ivar):       self.finder.toggleOption(ivar)
    #@-node:ekr.20060124122844:get/set/toggleOption (minibufferFind)
    #@+node:ekr.20060125074939:showFindOptions
    def showFindOptions (self):

        '''Show the present find options in the status line.'''

        frame = self.c.frame ; z = []
        # Set the scope field.
        head  = self.getOption('search_headline')
        body  = self.getOption('search_body')
        scope = self.getOption('radio-search-scope')
        d = {'entire-outline':'all','suboutline-only':'tree','node-only':'node'}
        scope = d.get(scope) or ''
        head = g.choose(head,'head','')
        body = g.choose(body,'body','')
        sep = g.choose(head and body,'+','')

        frame.clearStatusLine()
        s = '%s%s%s %s  ' % (head,sep,body,scope)
        frame.putStatusLine(s,color='blue')

        # Set the type field.
        script = self.getOption('script_search')
        regex  = self.getOption('pattern_match')
        change = self.getOption('script_change')
        if script:
            s1 = '*Script-find'
            s2 = g.choose(change,'-change*','*')
            z.append(s1+s2)
        elif regex: z.append('regex')

        table = (
            ('reverse',         'reverse'),
            ('ignore_case',     'noCase'),
            ('whole_word',      'word'),
            ('wrap',            'wrap'),
            ('mark_changes',    'markChg'),
            ('mark_finds',      'markFnd'),
        )

        for ivar,s in table:
            val = self.getOption(ivar)
            if val: z.append(s)

        frame.putStatusLine(' '.join(z))
    #@-node:ekr.20060125074939:showFindOptions
    #@+node:ekr.20060205105950:setupChangePattern
    def setupChangePattern (self,pattern):

        # g.trace('pattern',g.callers(4))

        h = self.finder ; w = h.change_ctrl

        s = g.toUnicode(pattern)

        w.delete(0,'end')
        w.insert(0,s)

        h.update_ivars()
    #@-node:ekr.20060205105950:setupChangePattern
    #@+node:ekr.20060125091234:setupSearchPattern
    def setupSearchPattern (self,pattern):

        h = self.finder ; w = h.find_ctrl

        # g.trace(pattern,g.callers(4))

        s = g.toUnicode(pattern)

        w.delete(0,'end')
        w.insert(0,s)

        h.update_ivars()
    #@-node:ekr.20060125091234:setupSearchPattern
    #@-node:ekr.20060124140114: Options (minibufferFind)
    #@+node:ekr.20060210180352:addChangeStringToLabel
    def addChangeStringToLabel (self,protect=True):

        c = self.c ; k = c.k ; h = self.finder ; w = h.change_ctrl

        c.frame.log.selectTab('Find')
        c.minibufferWantsFocusNow()

        s = w.getAllText()

        while s.endswith('\n') or s.endswith('\r'):
            s = s[:-1]

        k.extendLabel(s,select=True,protect=protect)
    #@-node:ekr.20060210180352:addChangeStringToLabel
    #@+node:ekr.20060210164421:addFindStringToLabel
    def addFindStringToLabel (self,protect=True):

        c = self.c ; k = c.k ; h = self.finder ; w = h.find_ctrl

        c.frame.log.selectTab('Find')
        c.minibufferWantsFocusNow()

        s = w.getAllText()
        while s.endswith('\n') or s.endswith('\r'):
            s = s[:-1]

        k.extendLabel(s,select=True,protect=protect)
    #@-node:ekr.20060210164421:addFindStringToLabel
    #@+node:ekr.20070105123800:changeAll (minibufferFind)
    def changeAll (self,event):

        k = self.k ; tag = 'change-all' ; state = k.getState(tag)

        if state == 0:
            w = self.editWidget(event) # sets self.w
            if not w: return
            # Bug fix: 2009-5-31.
            # None denotes that we use the present value of the option.
            self.setupArgs(forward=None,regexp=None,word=None)
            k.setLabelBlue('Change All From: ',protect=True)
            k.getArg(event,tag,1,self.changeAll)
        elif state == 1:
            self._sString = k.arg
            self.updateFindList(k.arg)
            s = 'Change All: %s With: ' % (self._sString)
            k.setLabelBlue(s,protect=True)
            self.addChangeStringToLabel()
            k.getArg(event,tag,2,self.changeAll,completion=False,prefix=s)
        elif state == 2:
            self.updateChangeList(k.arg)
            self.lastStateHelper()
            self.generalChangeHelper(self._sString,k.arg,changeAll=True)
    #@nonl
    #@-node:ekr.20070105123800:changeAll (minibufferFind)
    #@+node:ekr.20060128080201:cloneFindAll
    def cloneFindAll (self,event):

        c = self.c ; k = self.k ; tag = 'clone-find-all'
        state = k.getState(tag)

        if state == 0:
            w = self.editWidget(event) # sets self.w
            if not w: return
            self.setupArgs(forward=None,regexp=None,word=None)
            k.setLabelBlue('Clone Find All: ',protect=True)
            k.getArg(event,tag,1,self.cloneFindAll)
        else:
            k.clearState()
            k.resetLabel()
            k.showStateAndMode()
            self.generalSearchHelper(k.arg,cloneFindAll=True)
            c.treeWantsFocus()
    #@-node:ekr.20060128080201:cloneFindAll
    #@+node:ekr.20060204120158:findAgain
    def findAgain (self,event):

        f = self.finder

        f.p = self.c.p
        f.v = self.finder.p.v

        # This handles the reverse option.
        return f.findAgainCommand()
    #@-node:ekr.20060204120158:findAgain
    #@+node:ekr.20060209064140:findAll
    def findAll (self,event):

        k = self.k ; state = k.getState('find-all')
        if state == 0:
            w = self.editWidget(event) # sets self.w
            if not w: return
            self.setupArgs(forward=True,regexp=False,word=True)
            k.setLabelBlue('Find All: ',protect=True)
            k.getArg(event,'find-all',1,self.findAll)
        else:
            k.clearState()
            k.resetLabel()
            k.showStateAndMode()
            self.generalSearchHelper(k.arg,findAll=True)
    #@-node:ekr.20060209064140:findAll
    #@+node:ekr.20060205105950.1:generalChangeHelper (minibufferFind)
    def generalChangeHelper (self,find_pattern,change_pattern,changeAll=False):

        # g.trace(repr(change_pattern))

        c = self.c

        self.setupSearchPattern(find_pattern)
        self.setupChangePattern(change_pattern)

        c.widgetWantsFocusNow(self.w)

        self.finder.p = self.c.p
        self.finder.v = self.finder.p.v

        # Bug fix: 2007-12-14: remove call to self.finder.findNextCommand.
        # This was the cause of replaces not starting in the right place!

        if changeAll:
            self.finder.changeAllCommand()
        else:
            # This handles the reverse option.
            self.finder.findNextCommand()
    #@-node:ekr.20060205105950.1:generalChangeHelper (minibufferFind)
    #@+node:ekr.20060124181213.4:generalSearchHelper
    def generalSearchHelper (self,pattern,cloneFindAll=False,findAll=False):

        c = self.c

        self.setupSearchPattern(pattern)

        c.widgetWantsFocusNow(self.w)

        self.finder.p = self.c.p
        self.finder.v = self.finder.p.v

        if findAll:
            self.finder.findAllCommand()
        elif cloneFindAll:
            self.finder.cloneFindAllCommand()
        else:
            # This handles the reverse option.
            self.finder.findNextCommand()
    #@-node:ekr.20060124181213.4:generalSearchHelper
    #@+node:ekr.20060210174441:lastStateHelper
    def lastStateHelper (self):

        k = self.k
        k.clearState()
        k.resetLabel()
        k.showStateAndMode()
    #@-node:ekr.20060210174441:lastStateHelper
    #@+node:ekr.20050920084036.113:replaceString
    def replaceString (self,event):

        k = self.k ; tag = 'replace-string' ; state = k.getState(tag)
        pattern_match = self.getOption ('pattern_match')
        prompt = 'Replace ' + g.choose(pattern_match,'Regex','String')
        if state == 0:
            self.setupArgs(forward=None,regexp=None,word=None)
            prefix = '%s: ' % prompt
            self.stateZeroHelper(event,tag,prefix,self.replaceString)
        elif state == 1:
            self._sString = k.arg
            self.updateFindList(k.arg)
            s = '%s: %s With: ' % (prompt,self._sString)
            k.setLabelBlue(s,protect=True)
            self.addChangeStringToLabel()
            k.getArg(event,'replace-string',2,self.replaceString,completion=False,prefix=s)
        elif state == 2:
            self.updateChangeList(k.arg)
            self.lastStateHelper()
            self.generalChangeHelper(self._sString,k.arg)
    #@-node:ekr.20050920084036.113:replaceString
    #@+node:ekr.20060124140224.3:reSearchBackward/Forward
    def reSearchBackward (self,event):

        k = self.k ; tag = 're-search-backward' ; state = k.getState(tag)

        if state == 0:
            self.setupArgs(forward=False,regexp=True,word=None)
            self.stateZeroHelper(
                event,tag,'Regexp Search Backward:',self.reSearchBackward,
                escapes=[self.replaceStringShortcut])
        elif k.getArgEscape:
            # Switch to the replace command.
            k.setState('replace-string',1,self.replaceString)
            self.replaceString(event=None)
        else:
            self.updateFindList(k.arg)
            self.lastStateHelper()
            self.generalSearchHelper(k.arg)

    def reSearchForward (self,event):

        k = self.k ; tag = 're-search-forward' ; state = k.getState(tag)
        if state == 0:
            self.setupArgs(forward=True,regexp=True,word=None)
            self.stateZeroHelper(
                event,tag,'Regexp Search:',self.reSearchForward,
                escapes=[self.replaceStringShortcut])
        elif k.getArgEscape:
            # Switch to the replace command.
            k.setState('replace-string',1,self.replaceString)
            self.replaceString(event=None)
        else:
            self.updateFindList(k.arg)
            self.lastStateHelper()
            self.generalSearchHelper(k.arg)
    #@-node:ekr.20060124140224.3:reSearchBackward/Forward
    #@+node:ekr.20060124140224.1:seachForward/Backward
    def searchBackward (self,event):

        k = self.k ; tag = 'search-backward' ; state = k.getState(tag)

        if state == 0:
            self.setupArgs(forward=False,regexp=False,word=False)
            self.stateZeroHelper(
                event,tag,'Search Backward: ',self.searchBackward,
                escapes=[self.replaceStringShortcut])
        elif k.getArgEscape:
            # Switch to the replace command.
            k.setState('replace-string',1,self.replaceString)
            self.replaceString(event=None)
        else:
            self.updateFindList(k.arg)
            self.lastStateHelper()
            self.generalSearchHelper(k.arg)

    def searchForward (self,event):

        k = self.k ; tag = 'search-forward' ; state = k.getState(tag)

        if state == 0:
            self.setupArgs(forward=True,regexp=False,word=False)
            self.stateZeroHelper(
                event,tag,'Search: ',self.searchForward,
                escapes=[self.replaceStringShortcut])
        elif k.getArgEscape:
            # Switch to the replace command.
            k.setState('replace-string',1,self.replaceString)
            self.replaceString(event=None)
        else:
            self.updateFindList(k.arg)
            self.lastStateHelper()
            self.generalSearchHelper(k.arg)
    #@-node:ekr.20060124140224.1:seachForward/Backward
    #@+node:ekr.20060125093807:searchWithPresentOptions
    def searchWithPresentOptions (self,event):

        k = self.k ; tag = 'search-with-present-options'
        state = k.getState(tag)

        # g.trace('state',state)
        if state == 0:
            self.setupArgs(forward=None,regexp=None,word=None)
            self.stateZeroHelper(
                event,tag,'Search: ',self.searchWithPresentOptions,
                escapes=[self.replaceStringShortcut])
        elif k.getArgEscape:
            # Switch to the replace command.
            self.setupSearchPattern(k.arg) # 2010/01/10: update the find text immediately.
            k.setState('replace-string',1,self.replaceString)
            self.replaceString(event=None)
        else:
            self.updateFindList(k.arg)
            k.clearState()
            k.resetLabel()
            k.showStateAndMode()
            self.generalSearchHelper(k.arg)
    #@-node:ekr.20060125093807:searchWithPresentOptions
    #@+node:ekr.20060124134356:setupArgs
    def setupArgs (self,forward=False,regexp=False,word=False):

        h = self.finder ; k = self.k

        if forward is None:
            reverse = None
        else:
            reverse = not forward

        for ivar,val,in (
            ('reverse', reverse),
            ('pattern_match',regexp),
            ('whole_word',word),
        ):
            if val is not None:
                self.setOption(ivar,val)

        h.p = p = self.c.p
        h.v = p.v
        h.update_ivars()
        self.showFindOptions()
    #@-node:ekr.20060124134356:setupArgs
    #@+node:ekr.20060210173041:stateZeroHelper
    def stateZeroHelper (self,event,tag,prefix,handler,escapes=None):

        k = self.k
        self.w = self.editWidget(event)
        if not self.w: return

        k.setLabelBlue(prefix,protect=True)
        self.addFindStringToLabel(protect=False)

        # g.trace(escapes,g.callers())
        if escapes is None: escapes = []
        k.getArgEscapes = escapes
        k.getArgEscape = None # k.getArg may set this.
        k.getArg(event,tag,1,handler, # enter state 1
            tabList=self.findTextList,completion=True,prefix=prefix)
    #@-node:ekr.20060210173041:stateZeroHelper
    #@+node:ekr.20060224171851:updateChange/FindList
    def updateChangeList (self,s):

        if s not in self.changeTextList:
            self.changeTextList.append(s)

    def updateFindList (self,s):

        if s not in self.findTextList:
            self.findTextList.append(s)
    #@-node:ekr.20060224171851:updateChange/FindList
    #@+node:ekr.20060124140224.2:wordSearchBackward/Forward
    def wordSearchBackward (self,event):

        k = self.k ; tag = 'word-search-backward' ; state = k.getState(tag)

        if state == 0:
            self.setupArgs(forward=False,regexp=False,word=True)
            self.stateZeroHelper(event,tag,'Word Search Backward: ',self.wordSearchBackward)
        else:
            self.lastStateHelper()
            self.generalSearchHelper(k.arg)

    def wordSearchForward (self,event):

        k = self.k ; tag = 'word-search-forward' ; state = k.getState(tag)

        if state == 0:
            self.setupArgs(forward=True,regexp=False,word=True)
            self.stateZeroHelper(event,tag,'Word Search: ',self.wordSearchForward)
        else:
            self.lastStateHelper()
            self.generalSearchHelper(k.arg)
    #@-node:ekr.20060124140224.2:wordSearchBackward/Forward
    #@-others
#@-node:ekr.20060123125256:class minibufferFind (the findHandler)
#@+node:ekr.20050920084036.257:class searchCommandsClass
class searchCommandsClass (baseEditCommandsClass):

    '''Implements many kinds of searches.'''

    #@    @+others
    #@+node:ekr.20050920084036.258: ctor (searchCommandsClass)
    def __init__ (self,c):

        # g.trace('searchCommandsClass')

        baseEditCommandsClass.__init__(self,c)
            # init the base class.
            # sets self.c

        self.findTabHandler = None
        self.minibufferFindHandler = None
        self.inited = False

        # For isearch commands.
        self.ifinder = None
        self.stack = [] # Entries are (p,sel)
        self.ignoreCase = None
        self.forward = None
        self.regexp = None
    #@-node:ekr.20050920084036.258: ctor (searchCommandsClass)
    #@+node:ekr.20050920084036.259:getPublicCommands (searchCommandsClass)
    def getPublicCommands (self):

        return {
            'clone-find-all':                       self.cloneFindAll,
            'find-clone-all':                       self.cloneFindAll, # Synonym.

            'find-all':                             self.findAll,
            'change-all':                           self.changeAll,

            # Thin wrappers on Find tab
            'change':                               self.findTabChange,
            'change-then-find':                     self.findTabChangeThenFind,
            'find-next':                            self.findTabFindNext,
            'find-prev':                            self.findTabFindPrev,

            'hide-find-tab':                        self.hideFindTab,

            'isearch-forward':                      self.isearchForward,
            'isearch-backward':                     self.isearchBackward,
            'isearch-forward-regexp':               self.isearchForwardRegexp,
            'isearch-backward-regexp':              self.isearchBackwardRegexp,
            'isearch-with-present-options':         self.isearchWithPresentOptions,

            'open-find-tab':                        self.openFindTab,

            'replace-string':                       self.replaceString,

            're-search-forward':                    self.reSearchForward,
            're-search-backward':                   self.reSearchBackward,

            'search-again':                         self.findAgain,
            # Uses existing search pattern.

            'search-forward':                       self.searchForward,
            'search-backward':                      self.searchBackward,
            'search-with-present-options':          self.searchWithPresentOptions,
            # Prompts for search pattern.

            'set-find-everywhere':                  self.setFindScopeEveryWhere,
            'set-find-node-only':                   self.setFindScopeNodeOnly,
            'set-find-suboutline-only':             self.setFindScopeSuboutlineOnly,

            'show-find-options':                    self.showFindOptions,

            'toggle-find-collapses-nodes':          self.toggleFindCollapesNodes,

            'toggle-find-ignore-case-option':       self.toggleIgnoreCaseOption,
            'toggle-find-in-body-option':           self.toggleSearchBodyOption,
            'toggle-find-in-headline-option':       self.toggleSearchHeadlineOption,
            'toggle-find-mark-changes-option':      self.toggleMarkChangesOption,
            'toggle-find-mark-finds-option':        self.toggleMarkFindsOption,
            'toggle-find-regex-option':             self.toggleRegexOption,
            'toggle-find-reverse-option':           self.toggleReverseOption,
            'toggle-find-word-option':              self.toggleWholeWordOption,
            'toggle-find-wrap-around-option':       self.toggleWrapSearchOption,

            'word-search-forward':                  self.wordSearchForward,
            'word-search-backward':                 self.wordSearchBackward,
        }
    #@-node:ekr.20050920084036.259:getPublicCommands (searchCommandsClass)
    #@+node:ekr.20060123131421:Top-level methods
    #@+node:ekr.20051020120306:openFindTab
    def openFindTab (self,event=None,show=True):

        '''Open the Find tab in the log pane.'''

        c = self.c ; log = c.frame.log ; tabName = 'Find'

        wasOpen = self.inited

        if self.inited:
            log.selectTab(tabName)
        else:
            self.inited = True
            log.selectTab(tabName,createText=False)
            f = log.frameDict.get(tabName)
            self.findTabHandler = g.app.gui.createFindTab(c,f)

        if show or wasOpen or c.config.getBool('minibufferSearchesShowFindTab'):
            pass # self.findTabHandler.bringToFront()
        else:
            log.hideTab(tabName)
    #@-node:ekr.20051020120306:openFindTab
    #@+node:ekr.20051022212004:Find Tab commands
    # Just open the Find tab if it has never been opened.
    # For minibuffer commands, it would be good to force the Find tab to be visible.
    # However, this leads to unfortunate confusion when executed from a shortcut.

    def findTabChange(self,event=None):
        '''Execute the 'Change' command with the settings shown in the Find tab.'''
        if self.findTabHandler:
            self.findTabHandler.changeCommand()
        else:
            self.openFindTab()

    def findTabChangeThenFind(self,event=None):
        '''Execute the 'Replace, Find' command with the settings shown in the Find tab.'''
        if self.findTabHandler:
            self.findTabHandler.changeThenFindCommand()
        else:
            self.openFindTab()

    def findTabFindAll(self,event=None):
        '''Execute the 'Find All' command with the settings shown in the Find tab.'''
        if self.findTabHandler:
            self.findTabHandler.findAllCommand()
        else:
            self.openFindTab()

    def findTabFindNext (self,event=None):
        '''Execute the 'Find Next' command with the settings shown in the Find tab.'''
        if self.findTabHandler:
            self.findTabHandler.findNextCommand()
        else:
            self.openFindTab()

    def findTabFindPrev (self,event=None):
        '''Execute the 'Find Previous' command with the settings shown in the Find tab.'''
        if self.findTabHandler:
            self.findTabHandler.findPrevCommand()
        else:
            self.openFindTab()

    def hideFindTab (self,event=None):
        '''Hide the Find tab.'''
        if self.findTabHandler:
            self.c.frame.log.selectTab('Log')
    #@-node:ekr.20051022212004:Find Tab commands
    #@+node:ekr.20060124115801:getHandler
    def getHandler(self,show=False):

        '''Return the minibuffer handler, creating it if necessary.'''

        c = self.c

        self.openFindTab(show=show)
            # sets self.findTabHandler,
            # but *not* minibufferFindHandler.

        if not self.minibufferFindHandler:
            self.minibufferFindHandler = minibufferFind(c,self.findTabHandler)

        return self.minibufferFindHandler
    #@-node:ekr.20060124115801:getHandler
    #@+node:ekr.20060123115459:Find options wrappers
    def setFindScopeEveryWhere (self, event):
        '''Set the 'Entire Outline' radio button in the Find tab.'''
        return self.setFindScope('entire-outline')
    def setFindScopeNodeOnly  (self, event):
        '''Set the 'Node Only' radio button in the Find tab.'''
        return self.setFindScope('node-only')
    def setFindScopeSuboutlineOnly (self, event):
        '''Set the 'Suboutline Only' radio button in the Find tab.'''
        return self.setFindScope('suboutline-only')
    def showFindOptions (self,event):
        '''Show all Find options in the minibuffer label area.'''
        self.getHandler().showFindOptions()
    def toggleFindCollapesNodes(self,event):
        '''Toggle the 'Collapse Nodes' checkbox in the find tab.'''
        c = self.c
        c.sparse_find = not c.sparse_find
        if not g.unitTesting:
            g.es('sparse_find',c.sparse_find)
    def toggleIgnoreCaseOption     (self, event):
        '''Toggle the 'Ignore Case' checkbox in the Find tab.'''
        return self.toggleOption('ignore_case')
    def toggleMarkChangesOption (self, event):
        '''Toggle the 'Mark Changes' checkbox in the Find tab.'''
        return self.toggleOption('mark_changes')
    def toggleMarkFindsOption (self, event):
        '''Toggle the 'Mark Finds' checkbox in the Find tab.'''
        return self.toggleOption('mark_finds')
    def toggleRegexOption (self, event):
        '''Toggle the 'Regexp' checkbox in the Find tab.'''
        return self.toggleOption('pattern_match')
    def toggleReverseOption        (self, event):
        '''Toggle the 'Reverse' checkbox in the Find tab.'''
        return self.toggleOption('reverse')
    def toggleSearchBodyOption (self, event):
        '''Set the 'Search Body' checkbox in the Find tab.'''
        return self.toggleOption('search_body')
    def toggleSearchHeadlineOption (self, event):
        '''Toggle the 'Search Headline' checkbox in the Find tab.'''
        return self.toggleOption('search_headline')
    def toggleWholeWordOption (self, event):
        '''Toggle the 'Whole Word' checkbox in the Find tab.'''
        return self.toggleOption('whole_word')
    def toggleWrapSearchOption (self, event):
        '''Toggle the 'Wrap Around' checkbox in the Find tab.'''
        return self.toggleOption('wrap')
    def setFindScope (self, where):
        self.getHandler().setFindScope(where)
    def toggleOption (self, ivar):
        self.getHandler().toggleOption(ivar)
    #@-node:ekr.20060123115459:Find options wrappers
    #@+node:ekr.20060124093828:Find wrappers
    def changeAll(self,event=None):
        '''Execute the 'Change All' command with the settings shown in the Find tab.'''
        self.getHandler().changeAll(event)

    def cloneFindAll (self,event):
        '''Do search-with-present-options and print all matches in the log pane. It
        also creates a node at the beginning of the outline containing clones of all
        nodes containing the 'find' string. Only one clone is made of each node,
        regardless of how many clones the node has, or of how many matches are found
        in each node.'''
        self.getHandler().cloneFindAll(event)

    def findAll            (self,event):
        '''Do search-with-present-options and print all matches in the log pane.'''
        self.getHandler().findAll(event)

    def replaceString      (self,event):
        '''Prompts for a search string. Type <Return> to end the search string. The
        command will then prompt for the replacement string. Typing a second
        <Return> key will place both strings in the Find tab and executes a **find**
        command, that is, the search-with-present-options command.'''
        self.getHandler().replaceString(event)

    def reSearchBackward   (self,event):
        '''Set the 'Regexp' checkbox to True and the 'Reverse' checkbox to True,
        then do search-with-present-options.'''
        self.getHandler().reSearchBackward(event)

    def reSearchForward    (self,event):
        '''Set the 'Regexp' checkbox to True, then do search-with-present-options.'''
        self.getHandler().reSearchForward(event)

    def searchBackward     (self,event):
        '''Set the 'Word Search' checkbox to False and the 'Reverse' checkbox to True,
        then do search-with-present-options.'''
        self.getHandler().searchBackward(event)

    def searchForward      (self,event):
        '''Set the 'Word Search' checkbox to False, then do search-with-present-options.'''
        self.getHandler().searchForward(event)

    def wordSearchBackward (self,event):
        '''Set the 'Word Search' checkbox to True, then do search-with-present-options.'''
        self.getHandler().wordSearchBackward(event)

    def wordSearchForward  (self,event):
        '''Set the Word Search' checkbox to True and the 'Reverse' checkbox to True,
        then do search-with-present-options.'''
        self.getHandler().wordSearchForward(event)

    def searchWithPresentOptions (self,event):
        '''Prompts for a search string. Typing the <Return> key puts the search
        string in the Find tab and executes a search based on all the settings in
        the Find tab. Recommended as the default search command.'''
        self.getHandler().searchWithPresentOptions(event)
    #@-node:ekr.20060124093828:Find wrappers
    #@+node:ekr.20060204120158.2:findAgain
    def findAgain (self,event):

        '''The find-again command is the same as the find-next command
        if the search pattern in the Find tab is not '<find pattern here>'
        Otherwise, the find-again is the same as the search-with-present-options command.'''

        h = self.getHandler()

        # h.findAgain returns False if there is no search pattern.
        # In that case, we revert to search-with-present-options.
        if not h.findAgain(event):
            h.searchWithPresentOptions(event)
    #@-node:ekr.20060204120158.2:findAgain
    #@-node:ekr.20060123131421:Top-level methods
    #@+node:ekr.20050920084036.261:incremental search...
    def isearchForward (self,event):
        '''Begin a forward incremental search.'''
        self.startIncremental(event,'isearch-forward',
            forward=True,ignoreCase=False,regexp=False)

    def isearchBackward (self,event):
        '''Begin a backward incremental search.'''
        self.startIncremental(event,'isearch-backward',
            forward=False,ignoreCase=False,regexp=False)

    def isearchForwardRegexp (self,event):
        '''Begin a forward incremental regexp search.'''
        self.startIncremental(event,'isearch-forward-regexp',
            forward=True,ignoreCase=False,regexp=True)

    def isearchBackwardRegexp (self,event):
        '''Begin a backard incremental regexp search.'''
        self.startIncremental(event,'isearch-backward-regexp',
            forward=False,ignoreCase=False,regexp=True)

    def isearchWithPresentOptions (self,event):
        '''Begin an incremental regexp search using find panel options.'''
        self.startIncremental(event,'isearch-with-present-options',
            forward=None,ignoreCase=None,regexp=None)
    #@+node:ekr.20090204084607.1:abortSearch
    def abortSearch (self):

        '''Restore the original position and selection.'''

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

        k.clearState()
        k.resetLabel()

        p,i,j,in_headline = self.stack[0]
        self.ifinder.in_headline = in_headline
        c.selectPosition(p)
        c.redraw_after_select(p)
        c.bodyWantsFocusNow()
        w.setSelectionRange(i,j)

        # g.trace(p.h,i,j)
    #@-node:ekr.20090204084607.1:abortSearch
    #@+node:ekr.20060203072636:endSearch
    def endSearch (self):

        c,k = self.c,self.k

        k.clearState()
        k.resetLabel()
        c.bodyWantsFocusNow()
    #@-node:ekr.20060203072636:endSearch
    #@+node:ekr.20090204084607.2:iSearch
    def iSearch (self,again=False):

        '''Handle the actual incremental search.'''

        c = self.c ; k = self.k ; p = c.p
        ifinder = self.ifinder
        reverse = not self.forward
        pattern = k.getLabel(ignorePrompt=True)
        if not pattern:
            return self.abortSearch()
        ifinder.c = c ; ifinder.p = p.copy()
        # Get the base ivars from the find tab.
        ifinder.update_ivars()
        # Save
        oldPattern = ifinder.find_text
        oldRegexp  = ifinder.pattern_match
        oldReverse = ifinder.reverse
        oldWord =  ifinder.whole_word
        # Override
        ifinder.pattern_match = self.regexp
        ifinder.reverse = reverse
        ifinder.find_text = pattern
        ifinder.whole_word = False # Word option can't be used!
        # Prepare the search.
        if len(self.stack) <= 1: ifinder.in_headline = False
        w = self.setWidget()
        s = w.getAllText()
        i,j = w.getSelectionRange()
        if again:   ins = g.choose(reverse,i,j+len(pattern))
        else:       ins = g.choose(reverse,j+len(pattern),i)
        ifinder.init_s_ctrl(s,ins)
        # Do the search!
        pos, newpos = ifinder.findNextMatch()
        # Restore.
        ifinder.find_text = oldPattern
        ifinder.pattern_match = oldRegexp
        ifinder.reverse = oldReverse
        ifinder.whole_word = oldWord
        # Handle the results of the search.
        if pos is not None: # success.
            w = ifinder.showSuccess(pos,newpos,showState=False)
            if w: i,j = w.getSelectionRange(sort=False)
            # else: g.trace('****')
            if not again: self.push(c.p,i,j,ifinder.in_headline)
        elif ifinder.wrapping:
            g.es("end of wrapped search")
        else:
            g.es("not found","'%s'" % (pattern))
            event = g.Bunch(char='\b',keysym='\b',stroke='BackSpace')
            k.updateLabel(event)
    #@-node:ekr.20090204084607.2:iSearch
    #@+node:ekr.20050920084036.264:iSearchStateHandler
    # Called from the state manager when the state is 'isearch'

    def iSearchStateHandler (self,event):

        trace = False and not g.unitTesting
        c = self.c ; k = self.k
        stroke = event.stroke
        if trace: g.trace('stroke',repr(stroke))

        # No need to recognize ctrl-z.
        if stroke in ('Escape','Return'):
            self.endSearch()
        elif stroke in self.iSearchStrokes:
            self.iSearch(again=True)
        elif stroke == 'BackSpace':
            k.updateLabel(event)
            self.iSearchBackspace()
        elif stroke.startswith('Ctrl+') or stroke.startswith('Alt+'):
            # End the search and execute the command.
            self.endSearch()
            k.masterKeyHandler(event,stroke=stroke)
        else:
            if trace: g.trace('event',event)
            k.updateLabel(event)
            self.iSearch()
    #@nonl
    #@-node:ekr.20050920084036.264:iSearchStateHandler
    #@+node:ekr.20090204084607.4:iSearchBackspace
    def iSearchBackspace (self):

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

        if len(self.stack) <= 1:
            self.abortSearch()
            return

        # Reduce the stack by net 1.
        junk = self.pop()
        p,i,j,in_headline = self.pop()
        self.push(p,i,j,in_headline)
        if trace: g.trace(p.h,i,j,in_headline)

        if in_headline:
            # Like ifinder.showSuccess.
            selection = i,j,i
            c.redrawAndEdit(p,selectAll=False,
                selection=selection,
                keepMinibuffer=True)
        else:
            c.selectPosition(p)
            w = c.frame.body.bodyCtrl
            c.bodyWantsFocusNow()
            if i > j: i,j = j,i
            w.setSelectionRange(i,j)

        if len(self.stack) <= 1:
            self.abortSearch()



    #@-node:ekr.20090204084607.4:iSearchBackspace
    #@+node:ekr.20090204084607.6:getStrokes
    def getStrokes (self,commandName):

        c = self.c

        aList = self.inverseBindingDict.get(commandName,[])
        return [key for pane,key in aList]
    #@-node:ekr.20090204084607.6:getStrokes
    #@+node:ekr.20090204084607.5:push & pop
    def push (self,p,i,j,in_headline):

        data = p.copy(),i,j,in_headline
        self.stack.append(data)

    def pop (self):

        data = self.stack.pop()
        p,i,j,in_headline = data
        return p,i,j,in_headline
    #@-node:ekr.20090204084607.5:push & pop
    #@+node:ekr.20090205085858.1:setWidget
    def setWidget (self):

        c = self.c ; p = c.currentPosition()
        bodyCtrl = c.frame.body.bodyCtrl
        ifinder = self.ifinder

        if ifinder.in_headline:
            w = c.edit_widget(p)
            if not w:
                # Selecting the minibuffer can kill the edit widget.
                selection = 0,0,0
                c.redrawAndEdit(p,selectAll=False,
                    selection=selection,keepMinibuffer=True)
                w = c.edit_widget(p)
            if not w: # Should never happen.
                g.trace('**** no edit widget!')
                ifinder.in_headline = False ; w = bodyCtrl
        else:
            w = bodyCtrl
        if w == bodyCtrl:
            c.bodyWantsFocusNow()
        return w
    #@-node:ekr.20090205085858.1:setWidget
    #@+node:ekr.20050920084036.262:startIncremental
    def startIncremental (self,event,commandName,forward,ignoreCase,regexp):

        c = self.c ; k = self.k

        # None is a signal to get the option from the find tab.

        if forward is None or not self.findTabHandler:
            self.openFindTab(show=False)

        self.ifinder = self.findTabHandler

        if not self.minibufferFindHandler:
            self.minibufferFindHandler = minibufferFind(c,self.findTabHandler)

        getOption = self.minibufferFindHandler.getOption

        self.event = event
        self.forward    = g.choose(forward is None,not getOption('reverse'),forward)
        self.ignoreCase = g.choose(ignoreCase is None,getOption('ignore_case'),ignoreCase)
        self.regexp     = g.choose(regexp  is None,getOption('pattern_match'),regexp)
        # Note: the word option can't be used with isearches!

        self.w = w = c.frame.body.bodyCtrl
        self.p1 = c.p.copy()
        self.sel1 = w.getSelectionRange(sort=False)
        i,j = self.sel1
        self.push(c.p,i,j,self.ifinder.in_headline)
        self.inverseBindingDict = k.computeInverseBindingDict()
        self.iSearchStrokes = self.getStrokes(commandName)

        k.setLabelBlue('Isearch%s%s%s: ' % (
                g.choose(self.forward,'',' Backward'),
                g.choose(self.regexp,' Regexp',''),
                g.choose(self.ignoreCase,' NoCase',''),
            ),protect=True)

        k.setState('isearch',1,handler=self.iSearchStateHandler)
        c.minibufferWantsFocusNow()
    #@-node:ekr.20050920084036.262:startIncremental
    #@-node:ekr.20050920084036.261:incremental search...
    #@-others
#@-node:ekr.20050920084036.257:class searchCommandsClass
#@-node:ekr.20051023094009:Search classes
#@+node:ekr.20051025071455:Spell classes
#@+others
#@+node:ekr.20051025071455.1:class spellCommandsClass
class spellCommandsClass (baseEditCommandsClass):

    '''Commands to support the Spell Tab.'''

    #@    @+others
    #@+node:ekr.20051025080056:ctor
    def __init__ (self,c):

        baseEditCommandsClass.__init__(self,c) # init the base class.

        self.handler = None

        # All the work happens when we first open the frame.
    #@-node:ekr.20051025080056:ctor
    #@+node:ekr.20051025080420:getPublicCommands (searchCommandsClass)
    def getPublicCommands (self):

        return {
            'open-spell-tab':           self.openSpellTab,
            'spell-find':               self.find,
            'spell-change':             self.change,
            'spell-change-then-find':   self.changeThenFind,
            'spell-ignore':             self.ignore,
            'hide-spell-tab':           self.hide,
        }
    #@-node:ekr.20051025080420:getPublicCommands (searchCommandsClass)
    #@+node:ekr.20051025080633:openSpellTab
    def openSpellTab (self,event=None):

        '''Open the Spell Checker tab in the log pane.'''

        c = self.c ; log = c.frame.log ; tabName = 'Spell'

        if log.frameDict.get(tabName):
            log.selectTab(tabName)
        else:
            log.selectTab(tabName)
            self.handler = spellTabHandler(c,tabName)
            if not self.handler.loaded:
                log.deleteTab(tabName,force=True)
    #@-node:ekr.20051025080633:openSpellTab
    #@+node:ekr.20051025080420.1:commands...(spellCommandsClass)
    # Just open the Spell tab if it has never been opened.
    # For minibuffer commands, we must also force the Spell tab to be visible.
    # self.handler is a spellTabHandler object (inited by openSpellTab)

    def find (self,event=None):
        '''Simulate pressing the 'Find' button in the Spell tab.'''
        if self.handler:
            self.openSpellTab()
            self.handler.find()
        else:
            self.openSpellTab()

    def change(self,event=None):
        '''Simulate pressing the 'Change' button in the Spell tab.'''
        if self.handler:
            self.openSpellTab()
            self.handler.change()
        else:
            self.openSpellTab()

    def changeThenFind (self,event=None):
        '''Simulate pressing the 'Change, Find' button in the Spell tab.'''
        if self.handler:
            self.openSpellTab()
            # A workaround for a pylint warning:
            # self.handler.changeThenFind()
            f = getattr(self.handler,'changeThenFind')
            f()
        else:
            self.openSpellTab()

    def hide (self,event=None):
        '''Hide the Spell tab.'''
        if self.handler:
            self.c.frame.log.selectTab('Log')
            self.c.bodyWantsFocus()

    def ignore (self,event=None):
        '''Simulate pressing the 'Ignore' button in the Spell tab.'''
        if self.handler:
            self.openSpellTab()
            self.handler.ignore()
        else:
            self.openSpellTab()
    #@-node:ekr.20051025080420.1:commands...(spellCommandsClass)
    #@-others
#@-node:ekr.20051025071455.1:class spellCommandsClass
#@+node:ekr.20051025071455.18:class spellTabHandler (leoFind.leoFind)
class spellTabHandler (leoFind.leoFind):

    """A class to create and manage Leo's Spell Check dialog."""

    #@    @+others
    #@+node:ekr.20051025071455.19:Birth & death
    #@+node:ekr.20051025071455.20:spellTabHandler.__init__
    def __init__(self,c,tabName):

        """Ctor for the Leo Spelling dialog."""

        leoFind.leoFind.__init__(self,c) # Call the base ctor.

        self.c = c
        self.body = c.frame.body
        self.currentWord = None
        self.suggestions = []
        self.messages = [] # List of message to be displayed when hiding the tab.
        self.outerScrolledFrame = None
        self.workCtrl = g.app.gui.plainTextWidget(c.frame.top)
            # A text widget for scanning.
            # Must have a parent frame even though it is not packed.

        self.loaded = self.init_aspell(c)
        if self.loaded:
            self.tab = g.app.gui.createSpellTab(c,self,tabName)
    #@-node:ekr.20051025071455.20:spellTabHandler.__init__
    #@+node:ekr.20051025094004:init_aspell
    def init_aspell (self,c):

        '''Init aspell and related ivars.  Return True if all went well.'''

        self.local_language_code = c.config.getString('spell_local_language_code') or 'en'

        self.dictionaryFileName = dictionaryFileName = (
            c.config.getString('spell_local_dictionary') or
            os.path.join(g.app.loadDir,"..","plugins",'spellpyx.txt'))

        if not dictionaryFileName or not g.os_path_exists(dictionaryFileName):
            g.es_print('can not open dictionary file:',dictionaryFileName, color='red')
            return False

        self.aspell = AspellClass(c,dictionaryFileName,self.local_language_code)

        if self.aspell.aspell:
            self.dictionary = self.readDictionary(dictionaryFileName)
        else:
            self.dictionary = False
            # g.es_print('can not open Aspell',color='red')

        return self.aspell.aspell
    #@-node:ekr.20051025094004:init_aspell
    #@+node:ekr.20051025071455.16:readDictionary
    def readDictionary (self,fileName):

        """Read the dictionary of words which we use as a local dictionary

        Although Aspell itself has the functionality to handle this kind of things
        we duplicate it here so that we can also use it for the "ignore" functionality
        and so that in future a Python only solution could be developed."""

        d = {}

        try:
            f = open(fileName,"r")
        except IOError:
            g.es("can not open local dictionary",fileName,"using a blank one instead")
            return d

        try:
            # Create the dictionary - there are better ways to do this
            # in later Python's but we stick with this method for compatibility
            for word in f.readlines():
                word = g.toUnicode(word,reportErrors=True)
                d [word.strip().lower()] = 0
        finally:
            f.close()

        return d
    #@-node:ekr.20051025071455.16:readDictionary
    #@-node:ekr.20051025071455.19:Birth & death
    #@+node:ekr.20051025071455.36:Commands
    #@+node:ekr.20051025071455.37:add (spellTab)
    def add(self,event=None):
        """Add the selected suggestion to the dictionary."""

        if not self.currentWord: return

        # g.trace(self.currentWord)

        try:
            f = None
            try:
                # Rewrite the dictionary in alphabetical order.
                f = open(self.dictionaryFileName, "r")
                words = f.readlines()
                f.close()
                words = [word.strip() for word in words]
                words.append(self.currentWord)
                words.sort()
                f = open(self.dictionaryFileName, "w")
                for word in words:
                    f.write("%s\n" % g.toEncodedString(word,reportErrors=True))
                f.flush()
                f.close()
                if 1:
                    s = 'Spell: added %s' % self.currentWord
                    self.messages.append(s)
                else: # Too distracting.
                    g.es("adding ", color= "blue", newline= False) 
                    g.es('','%s' % self.currentWord)
            except IOError:
                g.es("can not add",self.currentWord,"to dictionary",color="red")
        finally:
            if f: f.close()

        self.dictionary[self.currentWord.lower()] = 0
        self.tab.onFindButton()
    #@-node:ekr.20051025071455.37:add (spellTab)
    #@+node:ekr.20051025071455.38:change (spellTab)
    def change(self,event=None):
        """Make the selected change to the text"""

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

        selection = self.tab.getSuggestion()
        if selection:
            if hasattr(self.tab,'change_i') and self.tab.change_i is not None:
                start,end = oldSel = self.tab.change_i,self.tab.change_j
                # g.trace('using',start,end)
            else:
                start,end = oldSel = w.getSelectionRange()
            if start is not None:
                if start > end: start,end = end,start
                w.delete(start,end)
                w.insert(start,selection)
                w.setSelectionRange(start,start+len(selection))
                c.frame.body.onBodyChanged("Change",oldSel=oldSel)
                c.invalidateFocus()
                c.bodyWantsFocusNow()
                return True

        # The focus must never leave the body pane.
        c.invalidateFocus()
        c.bodyWantsFocusNow()
        return False
    #@-node:ekr.20051025071455.38:change (spellTab)
    #@+node:ekr.20051025071455.40:find & helpers
    def find (self,event=None):
        """Find the next unknown word."""

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

        # Reload the work pane from the present node.
        s = w.getAllText().rstrip()
        self.workCtrl.delete(0,"end")
        self.workCtrl.insert("end",s)

        # Reset the insertion point of the work widget.
        ins = w.getInsertPoint()
        self.workCtrl.setInsertPoint(ins)

        alts, word = self.findNextMisspelledWord()
        self.currentWord = word # Need to remember this for 'add' and 'ignore'

        if alts:
            # Save the selection range.
            ins = w.getInsertPoint()
            i,j = w.getSelectionRange()
            self.tab.fillbox(alts,word)
            c.invalidateFocus()
            c.bodyWantsFocusNow()
            # Restore the selection range.
            w.setSelectionRange(i,j,insert=ins)
            w.see(ins)
        else:
            g.es("no more misspellings")
            self.tab.fillbox([])
            c.invalidateFocus()
            c.bodyWantsFocusNow()
    #@+node:ekr.20051025071455.45:findNextMisspelledWord
    def findNextMisspelledWord(self):
        """Find the next unknown word."""

        trace = False and not g.unitTesting
        c = self.c ; p = c.p
        w = c.frame.body.bodyCtrl
        aspell = self.aspell ; alts = None ; word = None
        try:
            while 1:
                i,j,p,word = self.findNextWord(p)
                # g.trace(i,j,p and p.h or '<no p>')
                if not p or not word:
                    alts = None
                    break
                #@            << Skip word if ignored or in local dictionary >>
                #@+node:ekr.20051025071455.46:<< Skip word if ignored or in local dictionary >>
                #@+at 
                #@nonl
                # We don't bother to call apell if the word is in 
                # our dictionary. The dictionary contains both 
                # locally 'allowed' words and 'ignored' words. We 
                # put the test before aspell rather than after 
                # aspell because the cost of checking aspell is 
                # higher than the cost of checking our local 
                # dictionary. For small local dictionaries this is 
                # probably not True and this code could easily be 
                # located after the aspell call
                #@-at
                #@@c

                if word.lower() in self.dictionary:
                    continue
                #@-node:ekr.20051025071455.46:<< Skip word if ignored or in local dictionary >>
                #@nl
                alts = aspell.processWord(word)
                if trace: g.trace('alts',alts and len(alts) or 0,i,j,word,p and p.h or 'None')
                if alts:
                    redraw = not p.isVisible(c)
                    # New in Leo 4.4.8: show only the 'sparse' tree when redrawing.
                    if c.sparse_spell and not c.p.isAncestorOf(p):
                        for p2 in c.p.self_and_parents():
                            p2.contract()
                            redraw = True
                    for p2 in p.parents():
                        if not p2.isExpanded():
                            p2.expand()
                            redraw = True
                    if redraw:
                        c.redraw(p)
                    else:
                        c.selectPosition(p)
                    w.setSelectionRange(i,j,insert=j)
                    break
        except Exception:
            g.es_exception()
        return alts, word
    #@-node:ekr.20051025071455.45:findNextMisspelledWord
    #@+node:ekr.20051025071455.47:findNextWord (spellTab)
    def findNextWord(self,p):
        """Scan for the next word, leaving the result in the work widget"""

        trace = False and not g.unitTesting
        c = self.c ; p = p.copy()
        while 1:
            s = self.workCtrl.getAllText()
            i = self.workCtrl.getInsertPoint()
            while i < len(s) and not g.isWordChar1(s[i]):
                i += 1
            # g.trace('p',p and p.h,'i',i,'len(s)',len(s))
            if i < len(s):
                # A non-empty word has been found.
                j = i
                while j < len(s) and g.isWordChar(s[j]):
                    j += 1
                word = s[i:j]
                # This trace verifies that all words have been checked.
                # g.trace(repr(word))
                for w in (self.workCtrl,c.frame.body.bodyCtrl):
                    c.widgetWantsFocusNow(w)
                    w.setSelectionRange(i,j,insert=j)
                if trace: g.trace(i,j,word,p.h)
                return i,j,p,word
            else:
                # End of the body text.
                p.moveToThreadNext()
                if not p: break
                self.workCtrl.delete(0,'end')
                self.workCtrl.insert(0,p.b)
                for w in (self.workCtrl,c.frame.body.bodyCtrl):
                    c.widgetWantsFocusNow(w)
                    w.setSelectionRange(0,0,insert=0)
                if trace: g.trace(0,0,'-->',p.h)

        return None,None,None,None
    #@nonl
    #@-node:ekr.20051025071455.47:findNextWord (spellTab)
    #@-node:ekr.20051025071455.40:find & helpers
    #@+node:ekr.20051025121408:hide
    def hide (self,event=None):

        self.c.frame.log.selectTab('Log')

        for message in self.messages:
            g.es(message,color='blue')

        self.messages = []
    #@-node:ekr.20051025121408:hide
    #@+node:ekr.20051025071455.41:ignore
    def ignore(self,event=None):

        """Ignore the incorrect word for the duration of this spell check session."""

        if not self.currentWord: return

        if 1: # Somewhat helpful: applies until the tab is destroyed.
            s = 'Spell: ignore %s' % self.currentWord
            self.messages.append(s)

        if 0: # Too distracting
            g.es("ignoring ",color= "blue", newline= False)
            g.es('','%s' % self.currentWord)

        self.dictionary[self.currentWord.lower()] = 0
        self.tab.onFindButton()
    #@-node:ekr.20051025071455.41:ignore
    #@-node:ekr.20051025071455.36:Commands
    #@-others
#@-node:ekr.20051025071455.18:class spellTabHandler (leoFind.leoFind)
#@+node:ekr.20051025071455.6:class AspellClass
class AspellClass:

    """A wrapper class for Aspell spell checker"""

    #@    @+others
    #@+node:ekr.20051025071455.7:Birth & death
    #@+node:ekr.20051025071455.8:__init__
    def __init__ (self,c,local_dictionary_file,local_language_code):

        """Ctor for the Aspell class."""

        self.c = c

        self.aspell_dir = c.os_path_finalize(c.config.getString('aspell_dir'))
        self.aspell_bin_dir = c.os_path_finalize(c.config.getString('aspell_bin_dir'))
        self.diagnose = c.config.getBool('diagnose-aspell-installation')

        self.local_language_code = local_language_code or 'en'
        self.local_dictionary_file = c.os_path_finalize(local_dictionary_file)
        self.local_dictionary = "%s.wl" % os.path.splitext(self.local_dictionary_file) [0]

        # g.trace('code',self.local_language_code,'dict',self.local_dictionary_file)
        # g.trace('dir',self.aspell_dir,'bin_dir',self.aspell_bin_dir)

        self.aspell = self.sc = None

        if ctypes:
            self.getAspellWithCtypes()
        else:
            self.getAspell() # Should never be needed.
    #@-node:ekr.20051025071455.8:__init__
    #@+node:ekr.20061017125710:getAspell
    def getAspell (self):

        # if sys.platform.startswith('linux'):
            # self.report('You must be using Python 2.5 or above to use aspell on Linux')
            # return

        try:
            import aspell
        except ImportError:
            # Specify the path to the top-level Aspell directory.
            theDir = g.choose(sys.platform=='darwin',self.aspell_dir,self.aspell_bin_dir)
            aspell = g.importFromPath('aspell',theDir,pluginName=None,verbose=False)

        # if not aspell:
            # self.report('can not import aspell')

        self.aspell = aspell
        self.sc = aspell and aspell.spell_checker(prefix=self.aspell_dir,lang=self.local_language_code)
    #@-node:ekr.20061017125710:getAspell
    #@+node:ekr.20061018111331:getAspellWithCtypes
    def getAspellWithCtypes (self):

        try:
            c_int, c_char_p = ctypes.c_int, ctypes.c_char_p

            if sys.platform.startswith('win'):
                path = g.os_path_join(self.aspell_bin_dir, "aspell-15.dll")
                self.aspell = aspell = ctypes.CDLL(path)
            else:
                path = 'aspell'
                libname = ctypes.util.find_library(path)
                assert(libname)
                self.aspell = aspell = ctypes.CDLL(libname)
        except Exception:
            self.report('Can not load %s' % (path))
            self.aspell = self.check = self.sc = None
            return

        try:
            #@        << define and configure aspell entry points >>
            #@+node:ekr.20061018111933:<< define and configure aspell entry points >>
            # new_aspell_config
            new_aspell_config = aspell.new_aspell_config 
            new_aspell_config.restype = c_int

            # aspell_config_replace
            aspell_config_replace = aspell.aspell_config_replace 
            aspell_config_replace.argtypes = [c_int, c_char_p, c_char_p] 

            # aspell_config_retrieve
            aspell_config_retrieve = aspell.aspell_config_retrieve 
            aspell_config_retrieve.restype = c_char_p  
            aspell_config_retrieve.argtypes = [c_int, c_char_p] 

            # aspell_error_message
            aspell_error_message = aspell.aspell_error_message 
            aspell_error_message.restype = c_char_p  

            sc = new_aspell_config()
            if 0:
                g.pr(sc )
                g.pr(aspell_config_replace(sc, "prefix", self.aspell_dir)) #1/0
                g.pr('prefix', self.aspell_dir, repr(aspell_config_retrieve(sc, "prefix")))
                g.pr(aspell_config_retrieve(sc, "lang"))
                g.pr(aspell_config_replace(sc, "lang",self.local_language_code))
                g.pr(aspell_config_retrieve(sc, "lang"))

            possible_err = aspell.new_aspell_speller(sc)
            aspell.delete_aspell_config(c_int(sc))

            # Rudimentary error checking, needs more.  
            if aspell.aspell_error_number(possible_err) != 0:
                self.report(aspell_error_message(possible_err))
                spell_checker = None
            else: 
                spell_checker = aspell.to_aspell_speller(possible_err)

            if not spell_checker:
                raise Exception('aspell checker not enabled')

            word_list_size = aspell.aspell_word_list_size
            word_list_size.restype = c_int
            word_list_size.argtypes = [c_int,]

            # word_list_elements
            word_list_elements = aspell.aspell_word_list_elements
            word_list_elements.restype = c_int
            word_list_elements.argtypes = [c_int,]

            # string_enumeration_next
            string_enumeration_next = aspell.aspell_string_enumeration_next
            string_enumeration_next.restype = c_char_p
            string_enumeration_next.argtypes = [c_int,]

            # check
            check = aspell.aspell_speller_check
            check.restype = c_int 
            check.argtypes = [c_int, c_char_p, c_int]

            # suggest
            suggest = aspell.aspell_speller_suggest
            suggest.restype = c_int 
            suggest.argtypes = [c_int, c_char_p, c_int]
            #@nonl
            #@-node:ekr.20061018111933:<< define and configure aspell entry points >>
            #@nl
        except Exception:
            self.report('aspell checker not enabled')
            self.aspell = self.check = self.sc = None
            return

        # Remember these functions (bound methods).
        # No other ctypes data is known outside this method.
        self.check = check
        self.spell_checker = spell_checker
        self.string_enumeration_next = string_enumeration_next
        self.suggest = suggest
        self.word_list_elements = word_list_elements
        self.word_list_size = word_list_size
    #@-node:ekr.20061018111331:getAspellWithCtypes
    #@+node:ekr.20071111153009:report
    def report (self,message):

        if self.diagnose:
            g.es_print(message,color='blue')
    #@-node:ekr.20071111153009:report
    #@-node:ekr.20051025071455.7:Birth & death
    #@+node:ekr.20051025071455.10:processWord AspellClass
    def processWord(self, word):
        """Pass a word to aspell and return the list of alternatives.
        OK: 
        * 
        Suggestions: 
        & <original> <count> <offset>: <miss>, <miss>, ... 
        None: 
        # <original> <offset> 
        simplifyed to not create the string then make a list from it
        """

        # g.trace('word',word)

        if not self.aspell:
            g.trace('aspell not installed')
            return None
        elif ctypes:
            # g.trace(type(word),word)
            word = g.toEncodedString(word)
            if self.check(self.spell_checker,word,len(word)):
                return None
            else:
                return self.suggestions(word)
        else:
            if self.sc.check(word):
                return None
            else:
                return self.sc.suggest(word)
    #@-node:ekr.20051025071455.10:processWord AspellClass
    #@+node:ekr.20061018101455.4:suggestions
    def suggestions(self,word):

        "return list of words found"

        aList = []
        sw = self.suggest(self.spell_checker, word, len(word))

        if self.word_list_size(sw):
            ewords = self.word_list_elements(sw)
            while 1: 
                x = self.string_enumeration_next(ewords)
                if x is None: break
                aList.append(x)
        return aList
    #@nonl
    #@-node:ekr.20061018101455.4:suggestions
    #@+node:ekr.20051025071455.11:updateDictionary
    def updateDictionary(self):

        """Update the aspell dictionary from a list of words.

        Return True if the dictionary was updated correctly."""

        try:
            # Create master list
            basename = os.path.splitext(self.local_dictionary)[0]
            cmd = (
                "%s --lang=%s create master %s.wl < %s.txt" %
                (self.aspell_bin_dir, self.local_language_code, basename,basename))
            os.popen(cmd)
            return True

        except Exception:
            junk, err, junk = sys.exc_info()
            g.pr("unable to update local aspell dictionary:",err)
            return False
    #@-node:ekr.20051025071455.11:updateDictionary
    #@-others
#@-node:ekr.20051025071455.6:class AspellClass
#@-others
#@-node:ekr.20051025071455:Spell classes
#@-others

#@<< define classesList >>
#@+node:ekr.20050922104213:<< define classesList >>
classesList = [
    ('abbrevCommands',      abbrevCommandsClass),
    ('bufferCommands',      bufferCommandsClass),
    ('editCommands',        editCommandsClass),
    ('chapterCommands',     chapterCommandsClass),
    ('controlCommands',     controlCommandsClass),
    ('debugCommands',       debugCommandsClass),
    ('editFileCommands',    editFileCommandsClass),
    ('helpCommands',        helpCommandsClass),
    ('keyHandlerCommands',  keyHandlerCommandsClass),
    ('killBufferCommands',  killBufferCommandsClass),
    ('leoCommands',         leoCommandsClass),
    ('macroCommands',       macroCommandsClass),
    # ('queryReplaceCommands',queryReplaceCommandsClass),
    ('rectangleCommands',   rectangleCommandsClass),
    ('registerCommands',    registerCommandsClass),
    ('searchCommands',      searchCommandsClass),
    ('spellCommands',       spellCommandsClass),
]
#@-node:ekr.20050922104213:<< define classesList >>
#@nl
#@-node:ekr.20050710142719:@thin leoEditCommands.py
#@-leo
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.