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

#@@language python 
#@@tabwidth -4

#@+all
#@+node:ekr.20071217134533:Pylint
#@+node:ekr.20071217231348:Pylint docs
@nocolor



















#@+node:ekr.20071217231348.1:options
General options

rcfile:	Specify a configuration file.
init-hook:	Python code to execute, usually for sys.path manipulation such as pygtk.require().
rpython-mode:	enable the rpython checker which is disabled by default
errors-only:	In debug mode, checkers without error messages are disabled and for others, only the ERROR messages are displayed, and no reports are done by default
profile:	Profiled execution.
ignore:	Add <file or directory> to the black list. It should be a base name, not a path. You may set this option multiple times. Default: CVS
persistent:	Pickle collected data for later comparisons. Default: yes
cache-size:	Set the cache size for astng objects. Default: 500
load-plugins:	List of plugins (as comma separated values of python modules names) to load, usually to register additional checkers.

Commands options

help-msg:	Display a help message for the given message id and exit. The value may be a comma separated list of message ids.
list-msgs:	Generate pylint's full documentation.
generate-rcfile:
 	Generate a sample configuration file according to the current configuration. You can put other options before this one to get them in the generated configuration.
generate-man:	Generate pylint's man page.

Messages control options

enable-checker:	Enable only checker(s) with the given id(s). This option conflict with the disable-checker option
disable-checker: Enable all checker(s) except those with the given id(s). This option conflict with the disable-checker option
enable-msg-cat:	Enable all messages in the listed categories.
disable-msg-cat: Disable all messages in the listed categories.
enable-msg:	Enable the message(s) with the given id(s).
disable-msg:	Disable the message(s) with the given id(s).

Reports options

output-format:	set the output format. Available formats are text, parseable, colorized, msvs (visual studio) and html Default: text
include-ids:	Include message's id in output
files-output:	Put messages in a separate file for each module / package specified on the command line instead of printing them on stdout. Reports (if any) will be written in a file name "pylint_global.[txt|html]".
reports:	Tells wether to display a full report or only the messages Default: yes
evaluation:	Python expression which should return a note less than 10 (10 is the highest note).You have access to the variables errors warning, statement which respectivly contain the number of errors / warnings messages and the total number of statements analyzed. This is used by the global evaluation report (R0004). Default: 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
comment:	Add a comment according to your evaluation note. This is used by the global evaluation report (R0004).
enable-report:	Enable the report(s) with the given id(s).
disable-report:	Disable the report(s) with the given id(s).
#@nonl
#@-node:ekr.20071217231348.1:options
#@+node:ekr.20071217231348.2:Main messages and reports
Main messages

E0001:	Used when a syntax error is raised for a module.
E0011:	Unrecognized file option %r Used when an unknown inline option is encountered.
E0012:	Bad option value %r Used when a bad value for an inline option is encountered.

I0001:	Unable to run raw checkers on built-in module %s Used to inform that a built-in module has not been checked using the raw checkers.
I0010:	Unable to consider inline option %r Used when an inline option is either badly formatted or can't be used inside modules.
I0011:	Locally disabling %s Used when an inline option disable a message or a messages category.
I0012:	Locally enabling %s Used when an inline option enable a message or a messages category.
I0013:	Ignoring entire file Used to inform that the file will not be checked
F0001:	Used when an error occured preventing the analyzing of a module (unable to find it for instance).
F0002:	%s: %s Used when an unexpected error occured while building the ASTNG representation. This is usually accomopagned by a traceback. Please report such errors !
F0003:	ignored builtin module %s Used to indicate that the user asked to analyze a builtin module which has been skipped.
F0004:	unexpected infered value %s Used to indicate that some value of an unexpected type has been infered.

Main reports

R0001:	Messages by category
R0002:	% errors / warnings by module
R0003:	Messages
R0004:	Global evaluation
#@nonl
#@-node:ekr.20071217231348.2:Main messages and reports
#@+node:ekr.20071217231348.3:Basic
Basic checker

Options

required-attributes:
 	Required attributes for module, separated by a comma
no-docstring-rgx:
 	Regular expression which should only match functions or classes name which do not require a docstring Default: __.*__
module-rgx:	Regular expression which should only match correct module names Default: (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
const-rgx:	Regular expression which should only match correct module level names Default: (([A-Z_][A-Z1-9_]*)|(__.*__))$
class-rgx:	Regular expression which should only match correct class names Default: [A-Z_][a-zA-Z0-9]+$
function-rgx:	Regular expression which should only match correct function names Default: [a-z_][a-z0-9_]{2,30}$
method-rgx:	Regular expression which should only match correct method names Default: [a-z_][a-z0-9_]{2,30}$
attr-rgx:	Regular expression which should only match correct instance attribute names Default: [a-z_][a-z0-9_]{2,30}$
argument-rgx:	Regular expression which should only match correct argument names Default: [a-z_][a-z0-9_]{2,30}$
variable-rgx:	Regular expression which should only match correct variable names Default: [a-z_][a-z0-9_]{2,30}$
inlinevar-rgx:	Regular expression which should only match correct list comprehension / generator expression variable names Default: [A-Za-z_][A-Za-z0-9_]*$
good-names:	Good variable names which should always be accepted, separated by a comma Default: i,j,k,ex,Run,_
bad-names:	Bad variable names which should always be refused, separated by a comma Default: foo,bar,baz,toto,tutu,tata
bad-functions:	List of builtins function names that should not be used, separated by a comma Default: map,filter,apply,input

Messages

E0100:	__init__ method is a generator Used when the special class method __init__ is turned into a generator by a yield in its body.
E0101:	Explicit return in __init__ Used when the special class method __init__ has an explicit return value.
E0102:	%s already defined line %s Used when a function / class / method is redefined.
E0103:	%r not properly in loop Used when break or continue keywords are used outside a loop.
E0104:	return outside function Used when a "return" statement is found outside a function or method.
E0105:	yield outside function Used when a "yield" statement is found outside a function or method.
E0106:	return with argument inside generator Used when a "return" statement with an argument is found outside in a generator function or method (e.g. with some "yield" statements).

W0101:	Unreachable code Used when there is some code behind a "return" or "raise" statement, which will never be accessed.
W0102:	Dangerous default value %s as argument Used when a mutable value as list or dictionary is detected in a default value for an argument.
W0104:	Statement seems to have no effect Used when a statement doesn't have (or at least seems to) any effect.
W0105:	String statement has no effect Used when a string is used as a statement (which of course has no effect). This is a particular case of W0104 with its own message so you can easily disable it if you're using those strings as documentation, instead of comments.
W0106:	Unnecessary semicolon Used when a statement is endend by a semi-colon (";"), which isn't necessary (that's python, not C ;).
W0107:	Unnecessary pass statement Used when a "pass" statement that can be avoided is encountered.)
W0122:	Use of the exec statement Used when you use the "exec" statement, to discourage its usage. That doesn't mean you can not use it !
W0141:	Used builtin function %r Used when a black listed builtin function is used (see the bad-function option). Usual black listed functions are the ones like map, or filter , where Python offers now some cleaner alternative like list comprehension.
W0142:	Used * or * magic* Used when a function or method is called using *args or **kwargs to dispatch arguments. This doesn't improve readility and should be used with care.

C0102:	Black listed name "%s" Used when the name is listed in the black list (unauthorized names).
C0103:	Invalid name "%s" (should match %s) Used when the name doesn't match the regular expression associated to its type (constant, variable, class...).
C0111:	Missing docstring Used when a module, function, class or method has no docstring. Some special methods like __init__ doesn't necessary require a docstring.
C0112:	Empty docstring Used when a module, function, class or method has an empty docstring (it would be too easy ;).
C0121:	Missing required attribute "%s" Used when an attribute required for modules is missing.

Reports

R0101:	Statistics by type
#@-node:ekr.20071217231348.3:Basic
#@+node:ekr.20071217231348.4:Type
Typecheck checker

Options

ignore-mixin-members:
 	Tells wether missing members accessed in mixin class should be ignored. A mixin class is detected if its name ends with "mixin" (case insensitive). Default: yes
zope:	When zope mode is activated, consider the acquired-members option to ignore access to some undefined attributes.
acquired-members:
 	List of members which are usually get through zope's acquisition mecanism and so shouldn't trigger E0201 when accessed (need zope=yes to be considered). Default: REQUEST,acl_users,aq_parent

Messages

E1101:	%s %r has no %r member  A variable is accessed for an unexistant member.
E1102:	%s is not callable      An object being called has been infered to a non callable object
E1103:	%s %r has no %r member (but some types could not be inferred) 
                               A variable is accessed for an unexistant member, but astng was not able to interpret all possible types of this variable.
E1111:	Assigning to function call which doesn't return     An assigment is done on a function call but the infered function doesn't return anything.
W1111:	Assigning to function call which only returns None  An assigment is done on a function call but the infered function returns nothing but None.
#@nonl
#@-node:ekr.20071217231348.4:Type
#@+node:ekr.20071217231348.5:Variables
Variables checker

Options

init-import:	Tells wether we should check for unused import in __init__ files.
dummy-variables-rgx:
 	A regular expression matching names used for dummy variables (i.e. not used). Default: _|dummy
additional-builtins:
 	List of additional names supposed to be defined in builtins. Remember that you should avoid to define new builtins when possible.

Messages

E0601:	Using variable %r before assignment Used when a local variable is accessed before it's assignment.
E0602:	Undefined variable %r Used when an undefined variable is accessed.
E0611:	No name %r in module %r Used when a name cannot be found in a module.

W0601:	Global variable %r undefined at the module level Used when a variable is defined through the "global" statement but the variable is not defined in the module scope.
W0602:	Using global for %r but no assigment is done Used when a variable is defined through the "global" statement but no assigment to this variable is done.
W0603:	Using the global statement Used when you use the "global" statement to update a global variable.
PyLint just try to discourage this usage. That doesn't mean you can not use it !
W0604:	Using the global statement at the module level Used when you use the "global" statement at the module level since it has no effect
W0611:	Unused import %s Used when an imported module or variable is not used.
W0612:	Unused variable %r Used when a variable is defined but not used.
W0613:	Unused argument %r Used when a function or method argument is not used.
W0614:	Unused import %s from wildcard import Used when an imported module or variable is not used from a 'from X import *' style import.

W????: Inline emphasis start-string without end-string.
W0621:	Redefining name %r from outer scope (line %s) Used when a variable's name hide a name defined in the outer scope.
W0622:	Redefining built-in %r Used when a variable or function override a built-in.
W0631:	Using possibly undefined loop variable %r Used when an loop variable (i.e. defined by a for loop or a list comprehension or a generator expression) is used outside the loop.
#@nonl
#@-node:ekr.20071217231348.5:Variables
#@+node:ekr.20071217231501:Classes
Classes checker

Options

ignore-iface-methods:
 	List of interface methods to ignore, separated by a comma. This is used for instance to not check methods defines in Zope's Interface base class. Default: isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
defining-attr-methods:
 	List of method names used to declare (i.e. assign) instance attributes. Default: __init__,__new__,setUp

Messages

E0202:	An attribute inherited from %s hide this method Used when a class defines a method which is hiden by an instance attribute from an ancestor class.
E0203:	Access to member %r before its definition line %s Used when an instance member is accessed before it's actually assigned.
E0211:	Method has no argument Used when a method which should have the bound instance as first argument has no argument defined.
E0213:	Method should have "self" as first argument Used when a method has an attribute different the "self" as first argument. This is considered as an error since this is a soooo common convention that you should'nt break it!
E0221:	Interface resolved to %s is not a class Used when a class claims to implement an interface which is not a class.
E0222:	Missing method %r from %s interface Used when a method declared in an interface is missing from a class implementing this interface

W0201:	Attribute %r defined outside __init__ Used when an instance attribute is defined outside the __init__ method.
W0211:	Static method with %r as first argument Used when a static method has "self" or "cls" as first argument.
W0212:	Access to a protected member %s of a client class Used when a protected member (i.e. class member with a name beginning with an underscore) is access outside the class or a descendant of the class where it's defined.
W0221:	Arguments number differs from %s method Used when a method has a different number of arguments than in the implemented interface or in an overridden method.
W0222:	Signature differs from %s method Used when a method signature is different than in the implemented interface or in an overridden method.
W0223:	Method %r is abstract in class %r but is not overridden Used when an abstract method (ie raise NotImplementedError) is not overridden in concrete class.
W0231:	__init__ method from base class %r is not called Used when an ancestor class method has an __init__ method which is not called by a derived class.
W0232:	Class has no __init__ method Used when a class has no __init__ method, neither its parent classes.
W0233:	__init__ method from a non direct base class %r is called Used when an __init__ method is called on a class which is not in the direct ancestors for the analysed class.
R0201:	Method could be a function Used when a method doesn't use its bound instance, and so could be written as a function.
C0202:	Class method should have "cls" as first argument Used when a class method has an attribute different than "cls" as first argument, to easily differentiate them from regular instance methods.
C0203:	Metaclass method should have "mcs" as first argument Used when a metaclass method has an attribute different the "mcs" as first argument.
F0202:	Unable to check methods signature (%s / %s) Used when PyLint has been unable to check methods signature compatibility for an unexpected raison. Please report this kind if you don't make sense of it.
F0220:	failed to resolve interfaces implemented by %s (%s) Used when a PyLint as failed to find interfaces implemented by a class
#@-node:ekr.20071217231501:Classes
#@+node:ekr.20071217231501.1:Design
Design checker

Options
max-args:	Maximum number of arguments for function / method Default: 5
max-locals:	Maximum number of locals for function / method body Default: 15
max-returns:	Maximum number of return / yield for function / method body Default: 6
max-branchs:	Maximum number of branch for function / method body Default: 12
max-statements:	Maximum number of statements in function / method body Default: 50
max-parents:	Maximum number of parents for a class (see R0901). Default: 7
max-attributes:	Maximum number of attributes for a class (see R0902). Default: 7
min-public-methods:
 	Minimum number of public methods for a class (see R0903). Default: 2
max-public-methods:
 	Maximum number of public methods for a class (see R0904). Default: 20

Messages

R0901:	Too many ancestors (%s/%s) Used when class has too many parent classes, try to reduce this to get a more simple (and so easier to use) class.
R0902:	Too many instance attributes (%s/%s) Used when class has too many instance attributes, try to reduce this to get a more simple (and so easier to use) class.
R0903:	Too few public methods (%s/%s) Used when class has too few public methods, so be sure it's really worth it.
R0904:	Too many public methods (%s/%s) Used when class has too many public methods, try to reduce this to get a more simple (and so easier to use) class.
R0911:	Too many return statements (%s/%s) Used when a function or method has too many return statement, making it hard to follow.
R0912:	Too many branches (%s/%s) Used when a function or method has too many branches, making it hard to follow.
R0913:	Too many arguments (%s/%s) Used when a function or method takes too many arguments.
R0914:	Too many local variables (%s/%s) Used when a function or method has too many local variables.
R0915:	Too many statements (%s/%s) Used when a function or method has too many statements. You should then split it in smaller functions / methods.
R0921:	Abstract class not referenced Used when an abstract class is not used as ancestor anywhere.
R0922:	Abstract class is only referenced %s times Used when an abstract class is used less than X times as ancestor.
R0923:	Interface not implemented Used when an interface class is not implemented anywhere.
#@nonl
#@-node:ekr.20071217231501.1:Design
#@+node:ekr.20071217231501.2:Imports
Imports checker

Options

deprecated-modules:
 	Deprecated modules which should not be used, separated by a comma Default: regsub,string,TERMIOS,Bastion,rexec
import-graph:	Create a graph of every (i.e. internal and external) dependencies in the given file (report R0402 must not be disabled)
ext-import-graph:
 	Create a graph of external dependencies in the given file (report R0402 must not be disabled)
int-import-graph:
 	Create a graph of internal dependencies in the given file (report R0402 must not be disabled)

Messages

W0401:	Wildcard import %s Used when from module import * is detected.
W0402:	Uses of a deprecated module %r Used a module marked as deprecated is imported.
W0403:	Relative import %r Used when an import relative to the package directory is detected.
W0404:	Reimport %r (imported line %s) Used when a module is reimported multiple times.
W0406:	Module import itself Used when a module is importing itself.
W0410:	__future__ import is not the first non docstring statement Python 2.5 and greater require __future__ import to be the first non docstring statement in the module.
R0401:	Cyclic import (%s) Used when a cyclic import between two or more modules is detected.
F0401:	Unable to import %r (%s) Used when pylint has been unable to import a module.

Reports

R0401:	External dependencies
R0402:	Modules dependencies graph
#@nonl
#@-node:ekr.20071217231501.2:Imports
#@+node:ekr.20071217231501.3:Newstyle
Newstyle checker


Messages

E1001:	Use __slots__ on an old style class Used when an old style class use the __slots__ attribute.
E1002:	Use super on an old style class Used when an old style class use the super builtin.
E1003:	Bad first argument %r given to super class Used when another argument than the current class is given as first argument of the super builtin.
E1010:	Raising a new style class Used when a new style class is raised since it's not possible with python < 2.5.
W1001:	Use of "property" on an old style class Used when PyLint detect the use of the builtin "property" on an old style class while this is relying on new style classes 
features
W1010:	Exception doesn't inherit from standard "Exception" class Used when a custom exception class is raised but doesn't inherit from the builtin "Exception" class.
#@nonl
#@-node:ekr.20071217231501.3:Newstyle
#@+node:ekr.20071217231828:Exceptions
Exceptions checker

Messages

E0701:	Bad except clauses order (%s) Used when except clauses are not in the correct order (from the more specific to the more generic). If you don't fix the order, some exceptions may not be catched by the most specific handler.
E0702:	Raising %s while only classes, instances or string are allowed Used when something which is neither a class, an instance or a string is raised (i.e. a TypeError will be raised).
W0701:	Raising a string exception Used when a string exception is raised.
W0702:	No exception's type specified Used when an except clause doesn't specify exceptions type to catch.
W0703:	Catch "Exception" Used when an except catch Exception instances.
W0704:	Except doesn't do anything Used when an except clause does nothing but "pass" and there is no "else" clause.
W0706:	Identifier %s used to raise an exception is assigned to %s Used when a variable used to raise an exception is initially assigned to a value which can't be used as an exception.
#@nonl
#@-node:ekr.20071217231828:Exceptions
#@+node:ekr.20071217231828.1:Format
Format checker

Options

max-line-length:
 	Maximum number of characters on a single line. Default: 80
max-module-lines:
 	Maximum number of lines in a module Default: 1000
indent-string:	String used as indentation unit. This is usually " " (4 spaces) or "t" (1 tab). Default: ' '

Messages

W0311:	Bad indentation. Found %s %s, expected %s Used when an unexpected number of indentation's tabulations or spaces has been found.
W0312:	Found indentation with %ss instead of %ss Used when there are some mixed tabs and spaces in a module.
W0331:	Use of the <> operator Used when the deprecated "<>" operator is used instead of "!=".
W0332:	Use l as long integer identifier Used when a lower case "l" is used to mark a long integer. You should use a upper case "L" since the letter "l" looks too much like the digit "1"
C0301:	Line too long (%s/%s) Used when a line is longer than a given number of characters.
C0302:	Too many lines in module (%s) Used when a module has too much lines, reducing its readibility.
C0321:	More than one statement on a single line Used when more than on statement are found on the same line.
C0322:	Operator not preceded by a space Used when one of the following operator (!= | <= | == | >= | < | > | = | += | -= | *= | /= | %) is not preceded by a space.
C0323:	Operator not followed by a space Used when one of the following operator (!= | <= | == | >= | < | > | = | += | -= | *= | /= | %) is not followed by a space.
C0324:	Comma not followed by a space Used when a comma (",") is not followed by a space.
F0321:	Format detection error in %r Used when an unexpected error occured in bad format detection. Please report the error if it occurs.
#@nonl
#@-node:ekr.20071217231828.1:Format
#@+node:ekr.20071217231828.2:Miscellaneous
Miscellaneous checker

Options

notes:	List of note tags to take in consideration, separated by a comma. Default: FIXME,XXX,TODO

Messages

E0501:	Non ascii characters found but no encoding specified (PEP 263) Used when some non ascii characters are detected but now encoding is specified, as explicited in the PEP 263.
E0502:	Wrong encoding specified (%s) Used when a known encoding is specified but the file doesn't seem to be actually in this encoding.
E0503:	Unknown encoding specified (%s) Used when an encoding is specified, but it's unknown to Python.
W0511:	Used when a warning note as FIXME or XXX is detected.
#@nonl
#@-node:ekr.20071217231828.2:Miscellaneous
#@+node:ekr.20071217231828.3:Metrics
Metrics checker


Reports

R0701:	Raw metrics
#@-node:ekr.20071217231828.3:Metrics
#@+node:ekr.20071217231828.4:Simularities
Similarities checker

min-similarity-lines:
 	Minimum lines number of a similarity. Default: 4
ignore-comments:
 	Ignore comments when computing similarities. Default: yes
ignore-docstrings:
 	Ignore docstrings when computing similarities. Default: yes

Messages

R0801:	Similar lines in %s files Indicates that a set of similar lines has been detected among multiple file. This usually means that the code should be refactored to avoid this duplication.

Reports

R0801:	Duplication
#@-node:ekr.20071217231828.4:Simularities
#@-node:ekr.20071217231348:Pylint docs
#@+node:ekr.20071220081732:pylint report
@killcolor


*** Erroneous errors in Leo 4.4.8



--- old --

************* Module leoTkinterTree

W0221:2377:leoTkinterTree.setSelectedLabelState: Arguments number differs from overridden method
W0221:2387:leoTkinterTree.setUnselectedLabelState: Arguments number differs from overridden method
#@+node:ekr.20080304084023:leoEditCommands
*** All these error messages are WRONG.

E1101:114:baseEditCommandsClass.endCommand: Instance of 'Bunch' has no 'name' member
E1101:115:baseEditCommandsClass.endCommand: Instance of 'Bunch' has no 'undoType' member
E1101:116:baseEditCommandsClass.endCommand: Instance of 'Bunch' has no 'oldSel' member
E1101:116:baseEditCommandsClass.endCommand: Instance of 'Bunch' has no 'oldText' member

E1101:8091:spellCommandsClass.openSpellTab: Instance of 'spellTabHandler' has no 'bringToFront' member
E1101:8121:spellCommandsClass.changeAll: Instance of 'spellTabHandler' has no 'changeAll' member
E1101:8129:spellCommandsClass.changeThenFind: Instance of 'spellTabHandler' has no 'changeThenFind' member
#@nonl
#@-node:ekr.20080304084023:leoEditCommands
#@+node:ekr.20080304084023.1:leoGlobals
*** All these error messages are WRONG.

E1101:137:computeGlobalConfigDir: Module 'sys' has no 'leo_config_directory' member
W0104:3915: Statement seems to have no effect

E0602:3931:getpreferredencoding: Undefined variable 'LC_CTYPE'
E0602:3932:getpreferredencoding: Undefined variable 'LC_CTYPE'
E1101:3933:getpreferredencoding: Module 'locale' has no 'nl_langinfo' member
E0602:3933:getpreferredencoding: Undefined variable 'CODESET'
E0602:3934:getpreferredencoding: Undefined variable 'LC_CTYPE'
E1101:3937:getpreferredencoding: Module 'locale' has no 'nl_langinfo' member
E0602:3937:getpreferredencoding: Undefined variable 'CODESET'
E1101:4432:mulderUpdateAlgorithm.copy_time: Module 'os' has no 'mtime' member
#@-node:ekr.20080304084023.1:leoGlobals
#@+node:ekr.20080304091441:leoTkinterFind
*** All these error messages are WRONG.

************* Module leoTkinterFind
E1101: 95:leoTkinterFind.__init__: Instance of 'leoTkinterFind' has no 'title' member
E1101:101:leoTkinterFind.__init__: Instance of 'leoTkinterFind' has no 'intKeys' member
E1101:104:leoTkinterFind.__init__: Instance of 'leoTkinterFind' has no 'newStringKeys' member
E1101:113:leoTkinterFind.__init__: Instance of 'leoTkinterFind' has no 'createTopFrame' member
E1101:115:leoTkinterFind.__init__: Instance of 'leoTkinterFind' has no 'top' member
E1101:116:leoTkinterFind.__init__: Instance of 'leoTkinterFind' has no 'top' member
E1101:123:leoTkinterFind.init: Instance of 'leoTkinterFind' has no 'intKeys' member
E1101:177:leoTkinterFind.destroySelf: Instance of 'leoTkinterFind' has no 'top' member
E1101:185:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'frame' member
E1101:213:leoTkinterFind.createFrame.setFocus: Instance of 'leoTkinterFind' has no 'c' member
E1101:298:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'resetWrap' member
E1101:305:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'resetWrap' member
E1103:325:leoTkinterFind.createFrame.findButtonCallback: Instance of 'leoTkinterFind' has no 'findButton' member (but some types c
ould not be inferred)
E1101:341:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'findAllButton' member
E1101:348:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'changeButton' member
E1101:354:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'changeThenFindButton' member
E1101:360:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'changeAllButton' member
E1101:367:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'top' member
E1101:368:leoTkinterFind.createFrame: Instance of 'leoTkinterFind' has no 'top' member
E1103:378:leoTkinterFind.createBindings.findButtonCallback2: Instance of 'leoTkinterFind' has no 'findButton' member (but some typ
es could not be inferred)
E1101:382:leoTkinterFind.createBindings: Instance of 'leoTkinterFind' has no 'resetWrap' member
E1101:383:leoTkinterFind.createBindings: Instance of 'leoTkinterFind' has no 'resetWrap' member
E1101:384:leoTkinterFind.createBindings: Instance of 'leoTkinterFind' has no 'selectAllFindText' member
E1101:396:leoTkinterFind.onCloseWindow: Instance of 'leoTkinterFind' has no 'top' member
E1101:401:leoTkinterFind.dismiss: Instance of 'leoTkinterFind' has no 'top' member
E1101:408:leoTkinterFind.bringToFront: Instance of 'leoTkinterFind' has no 'c' member
E1101:410:leoTkinterFind.bringToFront: Instance of 'leoTkinterFind' has no 'top' member
E1101:411:leoTkinterFind.bringToFront: Instance of 'leoTkinterFind' has no 'top' member
E1101:412:leoTkinterFind.bringToFront: Instance of 'leoTkinterFind' has no 'top' member
E1101:438:tkFindTab.initGui: Instance of 'tkFindTab' has no 'intKeys' member
E1101:441:tkFindTab.initGui: Instance of 'tkFindTab' has no 'newStringKeys' member
E1101:448:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'c' member
E1101:489:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'optionsOnly' member
E1101:504:tkFindTab.createFrame.setFocus: Instance of 'tkFindTab' has no 'c' member
E1101:538:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'optionsOnly' member
E1101:591:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'resetWrap' member
E1101:599:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'resetWrap' member
E1101:606:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'optionsOnly' member
E1101:623:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'findButtonCallback' member
E1101:624:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'findAllButton' member
E1101:626:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'changeButton' member
E1101:627:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'changeThenFindButton' member
E1101:628:tkFindTab.createFrame: Instance of 'tkFindTab' has no 'changeAllButton' member
E1101:647:tkFindTab.createBindings: Instance of 'tkFindTab' has no 'c' member
E1103:650:tkFindTab.createBindings.resetWrapCallback: Instance of 'tkFindTab' has no 'resetWrap' member (but some types could not
be inferred)
E1103:654:tkFindTab.createBindings.findButtonBindingCallback: Instance of 'tkFindTab' has no 'findButton' member (but some types c
ould not be inferred)
E1101:664:tkFindTab.createBindings: Instance of 'tkFindTab' has no 'hideTab' member
E1101:677:tkFindTab.init: Instance of 'tkFindTab' has no 'intKeys' member
E1101:746:tkFindTab.setOption: Instance of 'tkFindTab' has no 'intKeys' member
E1101:758:tkFindTab.toggleOption: Instance of 'tkFindTab' has no 'intKeys' member
"*****done*****"
Press any key to continue . . .
#@nonl
#@-node:ekr.20080304091441:leoTkinterFind
#@+node:ekr.20080304091441.1:leoTkinterGui
*** All these error messages are WRONG.

************* Module leoTkinterGui
E1101:179:tkinterGui.runMainLoop: Instance of 'tkinterGui' has no 'script' member
E1101:183:tkinterGui.runMainLoop: Instance of 'tkinterGui' has no 'script' member
#@-node:ekr.20080304091441.1:leoTkinterGui
#@+node:ekr.20080304092628:leoTkinterFrame
*** All these error messages are WRONG.

leoTkinterFrame.py Harmless: W0221: mismatch between Tk.Text methods and overridden methods.
leoTkinterFrame.py Dangerous: E1101
************* Module leoTkinterFrame
E1101: 53:leoTkinterBody.__init__: Instance of 'leoTkinterBody' has no 'c' member
E1101: 67:leoTkinterBody.createBindings: Instance of 'leoTkinterBody' has no 'frame' member
E1101: 67:leoTkinterBody.createBindings: Instance of 'leoTkinterBody' has no 'c' member
E1101: 88:leoTkinterBody.createControl: Instance of 'leoTkinterBody' has no 'c' member
E1101:102:leoTkinterBody.createControl: Instance of 'leoTkinterBody' has no 'editorWidgets' member
E1101:109:leoTkinterBody.createTextWidget: Instance of 'leoTkinterBody' has no 'c' member
E1101:176:leoTkinterBody.setColorFromConfig: Instance of 'leoTkinterBody' has no 'c' member
E1101:224:leoTkinterBody.setFontFromConfig: Instance of 'leoTkinterBody' has no 'c' member
E1101:241:leoTkinterBody.hasFocus: Instance of 'leoTkinterBody' has no 'frame' member
E1101:245:leoTkinterBody.setFocus: Instance of 'leoTkinterBody' has no 'c' member
E1101:351:leoTkinterBody.setEditorColors: Instance of 'leoTkinterBody' has no 'c' member
E1101:351:leoTkinterBody.setEditorColors: Instance of 'leoTkinterBody' has no 'editorWidgets' member
E1101:379:leoTkinterFrame.__init__: Class 'leoTkinterFrame' has no 'instances' member
E1101:437:leoTkinterFrame.finishCreate: Instance of 'leoTkinterFrame' has no 'initialRatios' member
E1101:439:leoTkinterFrame.finishCreate: Instance of 'leoTkinterFrame' has no 'createIconBar' member
E1101:442:leoTkinterFrame.finishCreate: Instance of 'leoTkinterFrame' has no 'createStatusLine' member
E1101:938:leoTkinterFrame.destroyAllPanels: Instance of 'leoTkinterFrame' has no 'comparePanel' member
E1101:938:leoTkinterFrame.destroyAllPanels: Instance of 'leoTkinterFrame' has no 'colorPanel' member
E1101:938:leoTkinterFrame.destroyAllPanels: Instance of 'leoTkinterFrame' has no 'fontPanel' member
E1101:938:leoTkinterFrame.destroyAllPanels: Instance of 'leoTkinterFrame' has no 'prefsPanel' member
E1101:1347:leoTkinterFrame.setMinibufferBindings: Instance of 'leoTkinterFrame' has no 'OnPaste' member
E1101:1901:leoTkinterFrame.toggleTkSplitDirection: Instance of 'leoTkinterFrame' has no 'initialRatios' member
E1101:2111:leoTkinterLog.createCanvasWidget: Instance of 'leoTkinterLog' has no 'logNumber' member
E1101:2138:leoTkinterLog.createTextWidget: Instance of 'leoTkinterLog' has no 'logNumber' member
E1101:2140:leoTkinterLog.createTextWidget: Instance of 'leoTkinterLog' has no 'logNumber' member
E1101:2303:leoTkinterLog.onActivateLog: Instance of 'leoTkinterLog' has no 'frame' member
E1101:2423:leoTkinterLog.createCanvas: Instance of 'leoTkinterLog' has no 'logNumber' member
E1101:2424:leoTkinterLog.createCanvas: Instance of 'leoTkinterLog' has no 'logNumber' member
E1101:2431:leoTkinterLog.createCanvas: Instance of 'leoTkinterLog' has no 'canvasDict' member
E1101:2432:leoTkinterLog.createCanvas: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2433:leoTkinterLog.createCanvas: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2462:leoTkinterLog.createTab: Instance of 'leoTkinterLog' has no 'canvasDict' member
E1101:2463:leoTkinterLog.createTab: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2464:leoTkinterLog.createTab: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2475:leoTkinterLog.createTab: Instance of 'leoTkinterLog' has no 'canvasDict' member
E1101:2476:leoTkinterLog.createTab: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2477:leoTkinterLog.createTab: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2489:leoTkinterLog.cycleTabFocus: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2514:leoTkinterLog.deleteTab: Instance of 'leoTkinterLog' has no 'canvasDict' member
E1101:2515:leoTkinterLog.deleteTab: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2516:leoTkinterLog.deleteTab: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2556:leoTkinterLog.numberOfVisibleTabs: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2573:leoTkinterLog.selectTab: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2574:leoTkinterLog.selectTab: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2587:leoTkinterLog.selectTab: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2589:leoTkinterLog.selectTab: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2600:leoTkinterLog.setTabBindings: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2600:leoTkinterLog.setTabBindings: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2787:leoTkinterLog.createColorPicker: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2788:leoTkinterLog.createColorPicker: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:2845:leoTkinterLog.createFontPicker: Instance of 'leoTkinterLog' has no 'frameDict' member
E1101:2846:leoTkinterLog.createFontPicker: Instance of 'leoTkinterLog' has no 'textDict' member
E1101:3018:leoTkinterTreeTab.createControl: Instance of 'leoTkinterTreeTab' has no 'c' member
E1101:3065:leoTkinterTreeTab.selectTab: Instance of 'leoTkinterTreeTab' has no 'cc' member
"*****done*****"
Press any key to continue . . .
#@nonl
#@-node:ekr.20080304092628:leoTkinterFrame
#@+node:ekr.20080304093027:leoTkinterKeys
*** All these error messages are WRONG.

************* Module leoTkinterKeys
E1101: 27:tkinterKeyHandlerClass.createTkIvars: Instance of 'tkinterKeyHandlerClass' has no 'useTextWidget' member
E1101: 27:tkinterKeyHandlerClass.createTkIvars: Instance of 'tkinterKeyHandlerClass' has no 'widget' member
E1101: 29:tkinterKeyHandlerClass.createTkIvars: Instance of 'tkinterKeyHandlerClass' has no 'widget' member
#@-node:ekr.20080304093027:leoTkinterKeys
#@+node:ekr.20080304093258:leoTkinterMenu
*** All these error messages are WRONG.

leoTkinterMenu.py
************* Module leoTkinterMenu
E1101: 62:leoTkinterMenu.computeMenuPositions: Instance of 'leoTkinterMenu' has no 'getMenu' member
E1101:136:leoTkinterMenu.insert: Instance of 'leoTkinterMenu' has no 'getMenu' member
E1101:172:leoTkinterMenu.clearAccel: Instance of 'leoTkinterMenu' has no 'getRealMenuName' member
E1101:183:leoTkinterMenu.createMenuBar: Instance of 'leoTkinterMenu' has no 'updateAllMenus' member
E1101:186:leoTkinterMenu.createMenuBar: Instance of 'leoTkinterMenu' has no 'setMenu' member
E1101:187:leoTkinterMenu.createMenuBar: Instance of 'leoTkinterMenu' has no 'createMenusFromTables' member
E1101:208:leoTkinterMenu.disableMenu: Instance of 'leoTkinterMenu' has no 'getRealMenuName' member
E1101:225:leoTkinterMenu.enableMenu: Instance of 'leoTkinterMenu' has no 'getRealMenuName' member
E1101:255:leoTkinterMenu.setMenuLabel: Instance of 'leoTkinterMenu' has no 'getRealMenuName' member
E1101:258:leoTkinterMenu.setMenuLabel: Instance of 'leoTkinterMenu' has no 'getRealMenuName' member
E1101:297:leoTkinterMenu.getMacHelpMenu: Instance of 'leoTkinterMenu' has no 'getMenu' member
E1101:301:leoTkinterMenu.getMacHelpMenu: Instance of 'leoTkinterMenu' has no 'createMenuEntries' member
#@-node:ekr.20080304093258:leoTkinterMenu
#@-node:ekr.20071220081732:pylint report
#@-node:ekr.20071217134533:Pylint
#@+node:ekr.20071211113202:4.4.6
#@+node:ekr.20071217092725:Beta 1
#@+node:ekr.20071211113202.1:Bugs fixed
#@+node:ekr.20071211082951:Fixed undo (dirty bit) problem)
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4668960
By: terry_n_brown

It seems in derived files you can delete some text and save, causing the derived
file to be written and all dirty flags cleared.  But then undo makes the text
come back, as expected, but save only saves the outline, because the dirty flag
wasn't set on the node in which the text was restored by undo.

@color
#@nonl
#@+node:ekr.20031218072017.2039:undo & helpers...
def undo (self,event=None):

    """Undo the operation described by the undo parameters."""

    u = self ; c = u.c
    # g.trace(g.callers(7))

    if not u.canUndo():
        # g.trace('cant undo',u.undoMenuLabel,u.redoMenuLabel)
        return
    if not u.getBead(u.bead):
        g.trace('no bead') ; return
    if not c.currentPosition():
        g.trace('no current position') ; return

    # g.trace(u.undoType)
    # g.trace(len(u.beads),u.bead,u.peekBead(u.bead))
    u.undoing = True
    u.groupCount = 0

    c.beginUpdate()
    try:
        c.endEditing()
        if u.undoHelper: u.undoHelper()
        else: g.trace('no undo helper for %s %s' % (u.kind,u.undoType))
    finally:
        c.frame.body.updateEditors() # New in Leo 4.4.8.
        if 0: # Don't do this: it interferes with selection ranges.
            # This strange code forces a recomputation of the root position.
            c.selectPosition(c.currentPosition())
        else:
            c.setCurrentPosition(c.currentPosition())
        c.setChanged(True)
        c.endUpdate()
        c.recolor_now()
        c.bodyWantsFocusNow()
        u.undoing = False
        u.bead -= 1
        u.setUndoTypes()
#@nonl
#@+node:ekr.20050424170219.1:undoClearRecentFiles
def undoClearRecentFiles (self):

    u = self ; c = u.c

    g.app.recentFiles = u.oldRecentFiles[:]
    c.recentFiles = u.oldRecentFiles[:]

    c.frame.menu.createRecentFilesMenuItems()
#@-node:ekr.20050424170219.1:undoClearRecentFiles
#@+node:ekr.20050412083057.1:undoCloneNode
def undoCloneNode (self):

    u = self ; c = u.c

    c.selectPosition(u.newP)
    c.deleteOutline()

    for v in u.dirtyVnodeList:
        v.t.setDirty() # Bug fix: Leo 4.4.6

    c.selectPosition(u.p)
#@-node:ekr.20050412083057.1:undoCloneNode
#@+node:ekr.20050412084055:undoDeleteNode
def undoDeleteNode (self):

    u = self ; c = u.c

    if u.oldBack:
        u.p.linkAfter(u.oldBack)
    elif u.oldParent:
        u.p.linkAsNthChild(u.oldParent,0)
    else:
        oldRoot = c.rootPosition()
        u.p.linkAsRoot(oldRoot)

    # Restore all vnodeLists (and thus all clone marks).
    u.p.restoreLinksInTree()
    u.p.setAllAncestorAtFileNodesDirty()
    c.selectPosition(u.p)
#@-node:ekr.20050412084055:undoDeleteNode
#@+node:ekr.20050318085713:undoGroup
def undoGroup (self):

    '''Process beads until the matching 'beforeGroup' bead is seen.'''

    u = self

    # Remember these values.
    c = u.c
    dirtyVnodeList = u.dirtyVnodeList or []
    oldSel = u.oldSel
    p = u.p.copy()

    u.groupCount += 1

    bunch = u.beads[u.bead] ; count = 0

    if not hasattr(bunch,'items'):
        g.trace('oops: expecting bunch.items.  bunch.kind = %s' % bunch.kind)
    else:
        # Important bug fix: 9/8/06: reverse the items first.
        reversedItems = bunch.items[:]
        reversedItems.reverse()
        c.beginUpdate()
        try:
            for z in reversedItems:
                self.setIvarsFromBunch(z)
                # g.trace(z.undoHelper)
                if z.undoHelper:
                    z.undoHelper() ; count += 1
                else:
                    g.trace('oops: no undo helper for %s' % u.undoType)
        finally:
            c.endUpdate(False)

    u.groupCount -= 1

    u.updateMarks('old') # Bug fix: Leo 4.4.6.

    for v in dirtyVnodeList:
        v.t.setDirty() # Bug fix: Leo 4.4.6.

    if not g.unitTesting:
        g.es("undo",count,"instances")

    c.selectPosition(p)
    if oldSel: c.frame.body.setSelectionRange(oldSel)
#@nonl
#@-node:ekr.20050318085713:undoGroup
#@+node:ekr.20050412083244:undoHoistNode & undoDehoistNode
def undoHoistNode (self):

    u = self ; c = u.c

    c.selectPosition(u.p)
    c.dehoist()

def undoDehoistNode (self):

    u = self ; c = u.c

    c.selectPosition(u.p)
    c.hoist()
#@-node:ekr.20050412083244:undoHoistNode & undoDehoistNode
#@+node:ekr.20050412085112:undoInsertNode
def undoInsertNode (self):

    u = self ; c = u.c

    c.selectPosition(u.newP)
    c.deleteOutline()

    if u.pasteAsClone:
        for bunch in u.beforeTree:
            t = bunch.t
            if u.p.v.t == t:
                c.setBodyString(u.p,bunch.body)
                c.setHeadString(u.p,bunch.head)
            else:
                t.setTnodeText(bunch.body)
                t.setHeadString(bunch.head)

    c.selectPosition(u.p)
#@-node:ekr.20050412085112:undoInsertNode
#@+node:ekr.20050526124906:undoMark
def undoMark (self):

    u = self ; c = u.c

    u.updateMarks('old')

    if u.groupCount == 0:

        for v in u.dirtyVnodeList:
            v.t.setDirty() # Bug fix: Leo 4.4.6.

        c.selectPosition(u.p)
#@-node:ekr.20050526124906:undoMark
#@+node:ekr.20050411112033:undoMove
def undoMove (self):

    u = self ; c = u.c

    # g.trace(u.p,u.oldParent,u.oldN)

    if u.oldParent:
        u.p.moveToNthChildOf(u.oldParent,u.oldN)
    elif u.oldBack:
        u.p.moveAfter(u.oldBack)
    else:
        u.p.moveToRoot(oldRoot=c.rootPosition())

    u.updateMarks('old')

    for v in u.dirtyVnodeList:
        v.t.setDirty() # Bug fix: Leo 4.4.6.

    c.selectPosition(u.p)
#@-node:ekr.20050411112033:undoMove
#@+node:ekr.20050318085713.1:undoNodeContents
def undoNodeContents (self):

    '''Undo all changes to the contents of a node,
    including headline and body text, and marked bits.
    '''

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

    u.p.setTnodeText(u.oldBody)
    w.setAllText(u.oldBody)
    c.frame.body.recolor(u.p,incremental=False)

    u.p.initHeadString(u.oldHead)
    c.frame.tree.setHeadline(u.p,u.oldHead)

    if u.groupCount == 0 and u.oldSel:
        u.c.frame.body.setSelectionRange(u.oldSel)

    u.updateMarks('old')

    for v in u.dirtyVnodeList:
        v.t.setDirty() # Bug fix: Leo 4.4.6.
#@-node:ekr.20050318085713.1:undoNodeContents
#@+node:ekr.20050318085713.2:undoTree
def undoTree (self):

    '''Redo replacement of an entire tree.'''

    u = self ; c = u.c

    u.p = self.undoRedoTree(u.p,u.newTree,u.oldTree)
    c.selectPosition(u.p) # Does full recolor.
    if u.oldSel:
        c.frame.body.setSelectionRange(u.oldSel)
#@-node:ekr.20050318085713.2:undoTree
#@+node:ekr.20050408100042:undoRedoTree
def undoRedoTree (self,p,new_data,old_data):

    '''Replace p and its subtree using old_data during undo.'''

    # Same as undoReplace except uses g.Bunch.

    u = self ; c = u.c

    if new_data == None:
        # This is the first time we have undone the operation.
        # Put the new data in the bead.
        bunch = u.beads[u.bead]
        bunch.newTree = u.saveTree(p.copy())
        u.beads[u.bead] = bunch

    # Replace data in tree with old data.
    u.restoreTree(old_data)
    c.setBodyString(p,p.bodyString())

    return p # Nothing really changes.
#@-node:ekr.20050408100042:undoRedoTree
#@+node:EKR.20040526090701.4:undoTyping
def undoTyping (self):

    u = self ; c = u.c ; current = c.currentPosition()
    w = c.frame.body.bodyCtrl

    # selectPosition causes recoloring, so don't do this unless needed.
    if current != u.p:
        c.selectPosition(u.p)
    elif u.undoType in ("Cut","Paste",'Clear Recent Files'):
        c.frame.body.forceFullRecolor()

    self.undoRedoText(
        u.p,u.leading,u.trailing,
        u.oldMiddleLines,u.newMiddleLines,
        u.oldNewlines,u.newNewlines,
        tag="undo",undoType=u.undoType)

    u.updateMarks('old')

    for v in u.dirtyVnodeList:
        v.t.setDirty() # Bug fix: Leo 4.4.6.

    if u.oldSel:
        c.bodyWantsFocusNow()
        i,j = u.oldSel
        w.setSelectionRange(i,j,insert=j)
    if u.yview:
        c.bodyWantsFocusNow()
        c.frame.body.setYScrollPosition(u.yview)
#@-node:EKR.20040526090701.4:undoTyping
#@+node:ekr.20031218072017.1493:undoRedoText (passed)
def undoRedoText (self,p,
    leading,trailing, # Number of matching leading & trailing lines.
    oldMidLines,newMidLines, # Lists of unmatched lines.
    oldNewlines,newNewlines, # Number of trailing newlines.
    tag="undo", # "undo" or "redo"
    undoType=None):

    # __pychecker__ = '--no-argsused' # newNewlines is unused, but it has symmetry.

    '''Handle text undo and redo: converts _new_ text into _old_ text.'''

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

    << Compute the result using p's body text >>
    p.setTnodeText(result)
    w.setAllText(result)
    c.frame.body.recolor(p,incremental=False)
#@+node:ekr.20061106105812.1:<< Compute the result using p's body text >>
# Recreate the text using the present body text.
body = p.bodyString()
body = g.toUnicode(body,"utf-8")
body_lines = body.split('\n')
s = []
if leading > 0:
    s.extend(body_lines[:leading])
if len(oldMidLines) > 0:
    s.extend(oldMidLines)
if trailing > 0:
    s.extend(body_lines[-trailing:])
s = string.join(s,'\n')
# Remove trailing newlines in s.
while len(s) > 0 and s[-1] == '\n':
    s = s[:-1]
# Add oldNewlines newlines.
if oldNewlines > 0:
    s = s + '\n' * oldNewlines
result = s

if u.debug_print:
    print "body:  ",body
    print "result:",result
#@-node:ekr.20061106105812.1:<< Compute the result using p's body text >>
#@-node:ekr.20031218072017.1493:undoRedoText (passed)
#@-node:ekr.20031218072017.2039:undo & helpers...
#@+node:ekr.20031218072017.2030:redo & helpers...
def redo (self,event=None):

    '''Redo the operation undone by the last undo.'''

    u = self ; c = u.c
    # g.trace(g.callers(7))

    if not u.canRedo():
        # g.trace('cant redo',u.undoMenuLabel,u.redoMenuLabel)
        return
    if not u.getBead(u.bead+1):
        g.trace('no bead') ; return
    if not c.currentPosition():
        g.trace('no current position') ; return

    # g.trace(u.undoType)
    # g.trace(u.bead+1,len(u.beads),u.peekBead(u.bead+1))
    u.redoing = True 
    u.groupCount = 0

    c.beginUpdate()
    try:
        c.endEditing()
        if u.redoHelper: u.redoHelper()
        else: g.trace('no redo helper for %s %s' % (u.kind,u.undoType))
    finally:
        c.frame.body.updateEditors() # New in Leo 4.4.8.
        if 0: # Don't do this: it interferes with selection ranges.
            # This strange code forces a recomputation of the root position.
            c.selectPosition(c.currentPosition())
        else:
            c.setCurrentPosition(c.currentPosition())
        c.setChanged(True)
        c.endUpdate()
        c.recolor_now()
        c.bodyWantsFocusNow()
        u.redoing = False
        u.bead += 1
        u.setUndoTypes()
#@nonl
#@+node:ekr.20050424170219:redoClearRecentFiles
def redoClearRecentFiles (self):

    u = self ; c = u.c

    g.app.recentFiles = u.newRecentFiles[:]
    c.recentFiles = u.newRecentFiles[:]

    c.frame.menu.createRecentFilesMenuItems()
#@-node:ekr.20050424170219:redoClearRecentFiles
#@+node:ekr.20050412083057:redoCloneNode
def redoCloneNode (self):

    u = self ; c = u.c

    if u.newBack:
        u.newP.linkAfter(u.newBack)
    elif u.newParent:
        u.newP.linkAsNthChild(u.newParent,0)
    else:
        oldRoot = c.rootPosition()
        u.newP.linkAsRoot(oldRoot)

    for v in u.dirtyVnodeList:
        v.t.setDirty()

    c.selectPosition(u.newP)
#@-node:ekr.20050412083057:redoCloneNode
#@+node:EKR.20040526072519.2:redoDeleteNode
def redoDeleteNode (self):

    u = self ; c = u.c

    c.selectPosition(u.p)
    c.deleteOutline()
    c.selectPosition(u.newP)
#@-node:EKR.20040526072519.2:redoDeleteNode
#@+node:ekr.20050412084532:redoInsertNode
def redoInsertNode (self):

    u = self ; c = u.c

    # g.trace('newP',u.newP.v,'back',u.newBack,'parent',u.newParent.v)

    if u.newBack:
        u.newP.linkAfter(u.newBack)
    elif u.newParent:
        u.newP.linkAsNthChild(u.newParent,0)
    else:
        oldRoot = c.rootPosition()
        u.newP.linkAsRoot(oldRoot)

    # Restore all vnodeLists (and thus all clone marks).
    u.newP.restoreLinksInTree()

    if u.pasteAsClone:
        for bunch in u.afterTree:
            t = bunch.t
            if u.newP.v.t == t:
                c.setBodyString(u.newP,bunch.body)
                c.setHeadString(u.newP,bunch.head)
            else:
                t.setTnodeText(bunch.body)
                t.setHeadString(bunch.head)
            # g.trace(t,bunch.head,bunch.body)

    c.selectPosition(u.newP)
#@-node:ekr.20050412084532:redoInsertNode
#@+node:ekr.20050412085138.1:redoHoistNode & redoDehoistNode
def redoHoistNode (self):

    u = self ; c = u.c

    c.selectPosition(u.p)
    c.hoist()

def redoDehoistNode (self):

    u = self ; c = u.c

    c.selectPosition(u.p)
    c.dehoist()
#@-node:ekr.20050412085138.1:redoHoistNode & redoDehoistNode
#@+node:ekr.20050318085432.6:redoGroup
def redoGroup (self):

    '''Process beads until the matching 'afterGroup' bead is seen.'''

    u = self

    # Remember these values.
    c = u.c
    dirtyVnodeList = u.dirtyVnodeList or []
    newSel = u.newSel
    p = u.p.copy()

    u.groupCount += 1

    bunch = u.beads[u.bead] ; count = 0
    if not hasattr(bunch,'items'):
        g.trace('oops: expecting bunch.items.  bunch.kind = %s' % bunch.kind)
    else:
        c.beginUpdate()
        try:
            for z in bunch.items:
                self.setIvarsFromBunch(z)
                if z.redoHelper:
                    # g.trace(z.redoHelper)
                    z.redoHelper() ; count += 1
                else:
                    g.trace('oops: no redo helper for %s' % u.undoType)
        finally:
            c.endUpdate(False)

    u.groupCount -= 1

    u.updateMarks('new') # Bug fix: Leo 4.4.6.

    for v in dirtyVnodeList:
        v.t.setDirty()

    if not g.unitTesting:
        g.es("redo",count,"instances")

    c.selectPosition(p)
    if newSel: c.frame.body.setSelectionRange(newSel)
#@nonl
#@-node:ekr.20050318085432.6:redoGroup
#@+node:ekr.20050318085432.7:redoNodeContents
def redoNodeContents (self):

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

    # Restore the body.
    u.p.setTnodeText(u.newBody)
    w.setAllText(u.newBody)
    c.frame.body.recolor(u.p,incremental=False)

    # Restore the headline.
    u.p.initHeadString(u.newHead)
    c.frame.tree.setHeadline(u.p,u.newHead) # New in 4.4b2.

    # g.trace('newHead',u.newHead,'revert',c.frame.tree.revertHeadline)

    if u.groupCount == 0 and u.newSel:
        u.c.frame.body.setSelectionRange(u.newSel)

    u.updateMarks('new')

    for v in u.dirtyVnodeList:
        v.t.setDirty()
#@-node:ekr.20050318085432.7:redoNodeContents
#@+node:ekr.20050526125801:redoMark
def redoMark (self):

    u = self ; c = u.c

    u.updateMarks('new')

    if u.groupCount == 0:

        for v in u.dirtyVnodeList:
            v.t.setDirty()

        c.selectPosition(u.p)
#@nonl
#@-node:ekr.20050526125801:redoMark
#@+node:ekr.20050411111847:redoMove
def redoMove (self):

    u = self ; c = u.c

    # g.trace(u.p)

    if u.newParent:
        u.p.moveToNthChildOf(u.newParent,u.newN)
    elif u.newBack:
        u.p.moveAfter(u.newBack)
    else:
        oldRoot = c.rootPosition()
        u.p.moveToRoot(oldRoot=oldRoot)

    u.updateMarks('new')

    for v in u.dirtyVnodeList:
        v.t.setDirty()

    c.selectPosition(u.p)
#@-node:ekr.20050411111847:redoMove
#@+node:ekr.20050318085432.8:redoTree
def redoTree (self):

    '''Redo replacement of an entire tree.'''

    u = self ; c = u.c

    u.p = self.undoRedoTree(u.p,u.oldTree,u.newTree)
    c.selectPosition(u.p) # Does full recolor.
    if u.newSel:
        c.frame.body.setSelectionRange(u.newSel)
#@-node:ekr.20050318085432.8:redoTree
#@+node:EKR.20040526075238.5:redoTyping
def redoTyping (self):

    u = self ; c = u.c ; current = c.currentPosition()
    w = c.frame.body.bodyCtrl

    # selectPosition causes recoloring, so avoid if possible.
    if current != u.p:
        c.selectPosition(u.p)
    elif u.undoType in ('Cut','Paste','Clear Recent Files'):
        c.frame.body.forceFullRecolor()

    self.undoRedoText(
        u.p,u.leading,u.trailing,
        u.newMiddleLines,u.oldMiddleLines,
        u.newNewlines,u.oldNewlines,
        tag="redo",undoType=u.undoType)

    u.updateMarks('new')

    for v in u.dirtyVnodeList:
        v.t.setDirty()

    if u.newSel:
        c.bodyWantsFocusNow()
        i,j = u.newSel
        w.setSelectionRange(i,j,insert=j)
    if u.yview:
        c.bodyWantsFocusNow()
        c.frame.body.setYScrollPosition(u.yview)
#@-node:EKR.20040526075238.5:redoTyping
#@-node:ekr.20031218072017.2030:redo & helpers...
#@-node:ekr.20071211082951:Fixed undo (dirty bit) problem)
#@+node:ekr.20071026052111:Fixed cursesGui  plugin
#@-node:ekr.20071026052111:Fixed cursesGui  plugin
#@+node:ekr.20071105155631.2:Fixed script_io_to_body plugin
@
Added k.overrideCommand.
#@nonl
#@+node:ekr.20031218072017.2140:c.executeScript & helpers
def executeScript(self,event=None,p=None,script=None,
    useSelectedText=True,define_g=True,define_name='__main__',silent=False):

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

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

    c = self ; script1 = script
    writeScriptFile = c.config.getBool('write_script_file')
    if not script:
        script = g.getScript(c,p,useSelectedText=useSelectedText)
    self.redirectScriptOutput()
    try:
        log = c.frame.log
        if script.strip():
            sys.path.insert(0,c.frame.openDirectory)
            script += '\n' # Make sure we end the script properly.
            # print '*** script\n',script
            try:
                p = c.currentPosition()
                d = g.choose(define_g,{'c':c,'g':g,'p':p},{})
                if define_name: d['__name__'] = define_name
                # A kludge: reset c.inCommand here to handle the case where we *never* return.
                # (This can happen when there are multiple event loops.)
                # This does not prevent zombie windows if the script puts up a dialog...
                c.inCommand = False
                # g.trace('**** before',writeScriptFile)
                if writeScriptFile:
                    scriptFile = self.writeScriptFile(script)
                    execfile(scriptFile,d)
                else:
                    exec script in d
                # g.trace('**** after')
                if not script1 and not silent:
                    # Careful: the script may have changed the log tab.
                    tabName = log and hasattr(log,'tabName') and log.tabName or 'Log'
                    g.es("end of script",color="purple",tabName=tabName)
            except Exception:
                g.handleScriptException(c,p,script,script1)
            del sys.path[0]
        else:
            tabName = log and hasattr(log,'tabName') and log.tabName or 'Log'
            g.es("no script selected",color="blue",tabName=tabName)
    finally:
        self.unredirectScriptOutput()
#@+node:ekr.20031218072017.2143:redirectScriptOutput
def redirectScriptOutput (self):

    c = self

    if c.config.redirect_execute_script_output_to_log_pane:

        g.redirectStdout() # Redirect stdout
        g.redirectStderr() # Redirect stderr
#@-node:ekr.20031218072017.2143:redirectScriptOutput
#@+node:EKR.20040627100424:unredirectScriptOutput
def unredirectScriptOutput (self):

    c = self

    if c.exists and c.config.redirect_execute_script_output_to_log_pane:

        g.restoreStderr()
        g.restoreStdout()
#@-node:EKR.20040627100424:unredirectScriptOutput
#@+node:ekr.20070115135502:writeScriptFile
def writeScriptFile (self,script):

    # Get the path to the file.
    c = self
    path = c.config.getString('script_file_path')
    if path:
        parts = path.split('/')
        path = g.app.loadDir
        for part in parts:
            path = g.os_path_abspath(g.os_path_join(path,part))
    else:
        path = g.os_path_abspath(g.os_path_join(g.app.loadDir,'..','test','scriptFile.py'))

    # Write the file.
    try:
        f = file(path,'w')
        f.write(script)
        f.close()
    except Exception:
        path = None

    return path
#@nonl
#@-node:ekr.20070115135502:writeScriptFile
#@-node:ekr.20031218072017.2140:c.executeScript & helpers
#@+node:ekr.20071212104050:k.overrideCommand
def overrideCommand (self,commandName,func):

    # Override entries in c.k.masterBindingsDict
    k = self
    d = k.masterBindingsDict
    for key in d.keys():
        d2 = d.get(key)
        for key2 in d2.keys():
            b = d2.get(key2)
            if b.commandName == commandName:
                b.func=func
                d2[key2] = b
#@-node:ekr.20071212104050:k.overrideCommand
#@+node:ekr.20051016101927:put & putnl (tkLog)
@ Printing uses self.logCtrl, so this code need not concern itself
with which tab is active.

Also, selectTab switches the contents of colorTags, so that is not concern.
It may be that Pmw will allow us to dispense with the colorTags logic...
#@+node:ekr.20031218072017.1473:put
# All output to the log stream eventually comes here.
def put (self,s,color=None,tabName='Log'):

    c = self.c

    # print 'tkLog.put',s
    # print 'tkLog.put',len(s),g.callers()

    if g.app.quitting or not c or not c.exists:
        return

    if tabName:
        self.selectTab(tabName)

    # Note: this must be done after the call to selectTab.
    w = self.logCtrl
    if w:
        << put s to log control >>
        self.logCtrl.update_idletasks()
    else:
        << put s to logWaiting and print s >>
#@+node:EKR.20040423082910:<< put s to log control >>
if color:
    if color not in self.colorTags:
        self.colorTags.append(color)
        w.tag_config(color,foreground=color)
    w.insert("end",s)
    w.tag_add(color,"end-%dc" % (len(s)+1),"end-1c")
    w.tag_add("black","end")
else:
    w.insert("end",s)

w.see('end')
self.forceLogUpdate(s)
#@-node:EKR.20040423082910:<< put s to log control >>
#@+node:EKR.20040423082910.1:<< put s to logWaiting and print s >>
g.app.logWaiting.append((s,color),)

print "Null tkinter log"

if type(s) == type(u""):
    s = g.toEncodedString(s,"ascii")

print s
#@-node:EKR.20040423082910.1:<< put s to logWaiting and print s >>
#@-node:ekr.20031218072017.1473:put
#@+node:ekr.20051016101927.1:putnl
def putnl (self,tabName='Log'):

    if g.app.quitting:
        return

    # print 'tkLog.putnl' # ,g.callers()

    if tabName:
        self.selectTab(tabName)

    w = self.logCtrl

    if w:
        w.insert("end",'\n')
        w.see('end')
        self.forceLogUpdate('\n')
    else:
        # Put a newline to logWaiting and print newline
        g.app.logWaiting.append(('\n',"black"),)
        print "Null tkinter log"
        print
#@-node:ekr.20051016101927.1:putnl
#@-node:ekr.20051016101927:put & putnl (tkLog)
#@+node:ekr.20031218072017.1474:enl, ecnl & ecnls
def ecnl(tabName='Log'):
    g.ecnls(1,tabName)

def ecnls(n,tabName='Log'):
    log = app.log
    if log and not log.isNull:
        while log.newlines < n:
            g.enl(tabName)

def enl(tabName='Log'):
    log = app.log
    if log and not log.isNull:
        log.newlines += 1
        log.putnl(tabName)
#@-node:ekr.20031218072017.1474:enl, ecnl & ecnls
#@+node:ekr.20070626132332:es & minitest
def es(s,*args,**keys):

    '''Put all non-keyword args to the log pane.
    The first, third, fifth, etc. arg translated by g.translateString.
    Supports color, comma, newline, spaces and tabName keyword arguments.
    '''
    # print 'es','app.log',repr(app.log),'log.isNull',not app.log or app.log.isNull,repr(s)
    # print 'es',repr(s)
    log = app.log
    if app.killed:
        return

    # Important: defining keyword arguments in addition to *args **does not work**.
    # See Section 5.3.4 (Calls) of the Python reference manual.
    # In other words, the following is about the best that can be done.
    color = keys.get('color')
    commas = keys.get('commas') ; commas = g.choose(commas=='True',True,False) # default is False
    newline = keys.get('newline') ; newline = g.choose(newline=='False',False,True) # default is True
    spaces= keys.get('spaces') ; spaces = g.choose(spaces=='False',False,True) # default is True
    tabName = keys.get('tabName','Log')

        # Default goes to log pane *not* the presently active pane.
    if color == 'suppress': return # New in 4.3.
    if type(s) != type("") and type(s) != type(u""):
        s = repr(s)
    s = g.translateArgs(s,args,commas,spaces)

    if app.batchMode:
        if app.log:
            app.log.put(s)
    elif g.unitTesting:
        if log and not log.isNull:
            s = g.toEncodedString(s,'ascii')
            if newline: print s
            else: print s,
    else:
        if log and log.isNull:
            pass
        elif log:
            log.put(s,color=color,tabName=tabName)
            for ch in s:
                if ch == '\n': log.newlines += 1
                else: log.newlines = 0
            if newline:
                g.ecnl(tabName=tabName) # only valid here
        elif newline:
            app.logWaiting.append((s+'\n',color),)
        else:
            app.logWaiting.append((s,color),)
#@+node:ekr.20071024101611:mini test of es
@nocolor
@first
@first
@

This doesn't work as an external unit test.
To test, select all following lines and do execute-script.

s1 = 'line1 Ä, ڱ,  궯, 奠 end'
s2 = g.toUnicode(s1,'utf-8')

for s in (s1,s2):
    g.es(s)
    g.es_print(s)
#@-node:ekr.20071024101611:mini test of es
#@-node:ekr.20070626132332:es & minitest
#@-node:ekr.20071105155631.2:Fixed script_io_to_body plugin
#@+node:ekr.20071212075450:Make Import to @file create @file nodes
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4671771
By: drtimcouper

Using v 4.4.5, I've tried to follow the programming tutorial to read in a python
file. File-Import to @file, select the file and I get a node

@nosent C:\..\.. .py

which is close, except I was expecting an @file, like the tutorial says, and
not an @nosent. The @nosent then has greyed-out Extract Section, so I'm stuck,
and can't create sections (well, I can add a <<section>> and Extract it, but
cannot see a way to refresh the code when I make changes externally to the file.

The same happens with a .java file too, FWIW.

@color
#@nonl
#@+node:ekr.20031218072017.2853:importAtFile
def importAtFile (self,event=None):

    '''Import one or more external files, creating @file trees.'''

    c = self

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

    names = g.app.gui.runOpenFileDialog(
        title="Import To @file",
        filetypes=types,
        defaultextension=".py",
        multiple=True)
    c.bringToFront()

    if names:
        c.importCommands.importFilesCommand(names,"@file")
#@-node:ekr.20031218072017.2853:importAtFile
#@+node:ekr.20031218072017.3212:importFilesCommand
def importFilesCommand (self,files=None,treeType=None):
    # Not a command.  It must *not* have an event arg.

    c = self.c
    if c == None: return
    v = current = c.currentVnode()
    if current == None: return
    if len(files) < 1: return
    self.tab_width = self.getTabWidth() # New in 4.3.
    self.treeType = treeType
    c.beginUpdate()
    try: # range of update...
        if len(files) == 2:
            << Create a parent for two files having a common prefix >>
        for fileName in files:
            g.setGlobalOpenDir(fileName)
            v = self.createOutline(fileName,current)
            if v: # createOutline may fail.
                if not g.unitTesting:
                    g.es("imported",fileName,color="blue")
                v.contract()
                v.setDirty()
                c.setChanged(True)
        c.validateOutline()
        current.expand()
    finally:
        c.endUpdate()
    c.selectVnode(current)
#@+node:ekr.20031218072017.3213:<< Create a parent for two files having a common prefix >>
@ The two filenames have a common prefix everything before the last period is the same.  For example, x.h and x.cpp.
@c

name0 = files[0]
name1 = files[1]
prefix0, junk = g.os_path_splitext(name0)
prefix1, junk = g.os_path_splitext(name1)
if len(prefix0) > 0 and prefix0 == prefix1:
    current = current.insertAsLastChild()
    # junk, nameExt = g.os_path_split(prefix1)
    name,junk = g.os_path_splitext(prefix1)
    current.initHeadString(name)
#@-node:ekr.20031218072017.3213:<< Create a parent for two files having a common prefix >>
#@-node:ekr.20031218072017.3212:importFilesCommand
#@+node:ekr.20031218072017.3210:createOutline (leoImport)
def createOutline (self,fileName,parent,atAuto=False,s=None,ext=None):

    c = self.c ; u = c.undoer ; s1 = s

    # New in Leo 4.4.7: honor @path directives.

    self.scanDefaultDirectory(parent) # sets .defaultDirectory.
    filename = g.os_path_join(self.default_directory,fileName)
    junk,self.fileName = g.os_path_split(fileName)
    self.methodName,self.fileType = g.os_path_splitext(self.fileName)
    self.setEncoding(p=parent,atAuto=atAuto)
    # g.trace(self.fileName,self.fileType)
    # All file types except the following just get copied to the parent node.
    if not ext: ext = self.fileType
    ext = ext.lower()
    if not s:
        << Read file into s >>

    << convert s to the proper encoding >>

    # Create the top-level headline.
    if atAuto:
        p = parent.copy()
        c.beginUpdate()
        try:
            p.setTnodeText('')
        finally:
            c.endUpdate(False)
    else:
        undoData = u.beforeInsertNode(parent)
        p = parent.insertAsLastChild()
        if self.treeType == "@file":
            p.initHeadString("@file " + fileName)
        else:
            # @root nodes don't have @root in the headline.
            p.initHeadString(fileName)
        u.afterInsertNode(p,'Import',undoData)

    self.rootLine = g.choose(self.treeType=="@file","","@root-code "+self.fileName+'\n')

    if ext in (".c", ".cpp", ".cxx"):
        self.scanCText(s,p,atAuto=atAuto)
    elif ext == '.c#':
        self.scanCSharpText(s,p,atAuto=atAuto)
    elif ext == ".el":
        self.scanElispText(s,p,atAuto=atAuto)
    elif ext == ".java":
        self.scanJavaText(s,p,atAuto=atAuto)
    elif ext == ".js":
        self.scanJavaScriptText(s,p,atAuto=atAuto)
    elif ext == ".pas":
        self.scanPascalText(s,p,atAuto=atAuto)
    elif ext in (".py", ".pyw"):
        self.scanPythonText(s,p,atAuto=atAuto)
    elif ext == ".php":
        self.scanPHPText(s,p,atAuto=atAuto)
    elif ext in ('.html','.htm','.xml'):
        self.scanXmlText(s,p,atAuto=atAuto)
    else:
        self.scanUnknownFileType(s,p,ext,atAuto=atAuto)

    p.contract()
    return p
#@+node:ekr.20031218072017.3211:<< Read file into s >>
try:
    fileName = g.os_path_normpath(fileName)
    theFile = open(fileName)
    s = theFile.read()
    theFile.close()
except IOError:
    z = g.choose(atAuto,'@auto ','')
    g.es("can not open", "%s%s" % (z,fileName),color='red')
    leoTest.fail()
    return None
#@-node:ekr.20031218072017.3211:<< Read file into s >>
#@+node:ekr.20080212092908:<< convert s to the proper encoding >>
if s and fileName.endswith('.py'):
    # Python's encoding comments override everything else.
    lines = g.splitLines(s)
    tag = '# -*- coding:' ; tag2 = '-*-'
    n1,n2 = len(tag),len(tag2)
    line1 = lines[0].strip()
    if line1.startswith(tag) and line1.endswith(tag2):
        e = line1[n1:-n2].strip()
        if e and g.isValidEncoding(e):
            # print 'found',e,'in',line1
            self.encoding = e

s = g.toUnicode(s,self.encoding)
#@-node:ekr.20080212092908:<< convert s to the proper encoding >>
#@-node:ekr.20031218072017.3210:createOutline (leoImport)
#@-node:ekr.20071212075450:Make Import to @file create @file nodes
#@+node:ekr.20071210092032:Fixed bug: rst3 plugin now finds default.css file correctly
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4667105
By: kayvan

I see that default.css is there (and it is there in my workspace).

The intent of that code seems to be to find the default.css in the same directory
as the leo file if it exists there. However, it does not look like it's working.

If I chdir to the doc/ directory before I start leo, everything works as
expected.

There has to be a way for us to get the rst3 plugin to see the default.css (in
the same directory as the LEO file that is being processed) when rst3_stylesheet_path
is not set.
#@nonl
#@-node:ekr.20071210092032:Fixed bug: rst3 plugin now finds default.css file correctly
#@+node:ekr.20071213095517:Fixed bug with clear-recent-files
# It now clears all .leoRecentFiles.txt files and write a message.
#@nonl
#@+node:ekr.20031218072017.2079:Recent Files submenu & allies
#@+node:ekr.20031218072017.2080:clearRecentFiles
def clearRecentFiles (self,event=None):

    """Clear the recent files list, then add the present file."""

    c = self ; f = c.frame ; u = c.undoer

    bunch = u.beforeClearRecentFiles()

    recentFilesMenu = f.menu.getMenu("Recent Files...")
    f.menu.delete_range(recentFilesMenu,0,len(c.recentFiles))

    c.recentFiles = []
    g.app.config.recentFiles = [] # New in Leo 4.3.
    f.menu.createRecentFilesMenuItems()
    c.updateRecentFiles(c.relativeFileName())

    g.app.config.appendToRecentFiles(c.recentFiles)

    # g.trace(c.recentFiles)

    u.afterClearRecentFiles(bunch)

    # New in Leo 4.4.5: write the file immediately.
    g.app.config.recentFileMessageWritten = False # Force the write message.
    g.app.config.writeRecentFilesFile(c)
#@-node:ekr.20031218072017.2080:clearRecentFiles
#@+node:ekr.20031218072017.2081:openRecentFile
def openRecentFile(self,name=None):

    if not name: return

    c = self ; v = c.currentVnode()
    << Set closeFlag if the only open window is empty >>

    fileName = name
    if not g.doHook("recentfiles1",c=c,p=v,v=v,fileName=fileName,closeFlag=closeFlag):
        ok, frame = g.openWithFileName(fileName,c)
        if ok and closeFlag:
            g.app.destroyWindow(c.frame) # 12/12/03
            c = frame.c # Switch to the new commander so the "recentfiles2" hook doesn't crash.
            c.setLog() # Sets the log stream for g.es

    g.doHook("recentfiles2",c=c,p=v,v=v,fileName=fileName,closeFlag=closeFlag)
#@+node:ekr.20031218072017.2082:<< Set closeFlag if the only open window is empty >>
@ If this is the only open window was opened when the app started, and the window has never been written to or saved, then we will automatically close that window if this open command completes successfully.
@c

closeFlag = (
    c.frame.startupWindow and # The window was open on startup
    not c.changed and not c.frame.saved and # The window has never been changed
    g.app.numberOfWindows == 1) # Only one untitled window has ever been opened
#@-node:ekr.20031218072017.2082:<< Set closeFlag if the only open window is empty >>
#@-node:ekr.20031218072017.2081:openRecentFile
#@+node:ekr.20031218072017.2083:c.updateRecentFiles
def updateRecentFiles (self,fileName):

    """Create the RecentFiles menu.  May be called with Null fileName."""

    if g.app.unitTesting: return

    def munge(name):
        return g.os_path_normpath(name or '').lower()
    def munge2(name):
        return g.os_path_abspath(g.os_path_join(g.app.loadDir,name or ''))

    # Update the recent files list in all windows.
    if fileName:
        compareFileName = munge(fileName)
        # g.trace(fileName)
        for frame in g.app.windowList:
            c = frame.c
            # Remove all versions of the file name.
            for name in c.recentFiles:
                if munge(fileName) == munge(name) or munge2(fileName) == munge2(name):
                    c.recentFiles.remove(name)
            c.recentFiles.insert(0,fileName)
            # g.trace('adding',fileName)
            # Recreate the Recent Files menu.
            frame.menu.createRecentFilesMenuItems()
    else:
        for frame in g.app.windowList:
            frame.menu.createRecentFilesMenuItems()
#@-node:ekr.20031218072017.2083:c.updateRecentFiles
#@-node:ekr.20031218072017.2079:Recent Files submenu & allies
#@+node:ekr.20050424114937.1:Reading and writing .leoRecentFiles.txt (g.app.config)
#@+node:ekr.20070224115832:readRecentFiles & helpers
def readRecentFiles (self,localConfigFile):

    '''Read all .leoRecentFiles.txt files.'''

    # The order of files in this list affects the order of the recent files list.
    seen = [] 
    localConfigPath = g.os_path_dirname(localConfigFile)
    for path in (
        g.app.homeDir,
        g.app.globalConfigDir,
        localConfigPath,
    ):
        if path and path not in seen:
            ok = self.readRecentFilesFile(path)
            if ok: seen.append(path)
    if not seen and self.write_recent_files_as_needed:
        self.createRecentFiles()
#@nonl
#@+node:ekr.20061010121944:createRecentFiles
def createRecentFiles (self):

    '''Trye to reate .leoRecentFiles.txt in
    - the users home directory first,
    - Leo's config directory second.'''

    for theDir in (g.app.homeDir,g.app.globalConfigDir):
        if theDir:
            try:
                fileName = g.os_path_join(theDir,'.leoRecentFiles.txt')
                f = file(fileName,'w')
                f.close()
                g.es_print('created',fileName,color='red')
                return
            except Exception:
                g.es_print('can not create',fileName,color='red')
                g.es_exception()
#@nonl
#@-node:ekr.20061010121944:createRecentFiles
#@+node:ekr.20050424115658:readRecentFilesFile
def readRecentFilesFile (self,path):

    fileName = g.os_path_join(path,'.leoRecentFiles.txt')
    ok = g.os_path_exists(fileName)
    if ok:
        if not g.unitTesting and not self.silent:
            print ('reading %s' % fileName)
        lines = file(fileName).readlines()
        if lines and self.munge(lines[0])=='readonly':
            lines = lines[1:]
        if lines:
            lines = [g.toUnicode(g.os_path_normpath(line),'utf-8') for line in lines]
            self.appendToRecentFiles(lines)

    return ok
#@nonl
#@-node:ekr.20050424115658:readRecentFilesFile
#@-node:ekr.20070224115832:readRecentFiles & helpers
#@+node:ekr.20050424114937.2:writeRecentFilesFile & helper
recentFileMessageWritten = False

def writeRecentFilesFile (self,c):

    '''Write the appropriate .leoRecentFiles.txt file.'''

    tag = '.leoRecentFiles.txt'

    if g.app.unitTesting:
        return

    localFileName = c.fileName()
    if localFileName:
        localPath,junk = g.os_path_split(localFileName)
    else:
        localPath = None

    written = False
    for path in (localPath,g.app.globalConfigDir,g.app.homeDir):
        if path:
            fileName = g.os_path_join(path,tag)
            if g.os_path_exists(fileName):
                if not self.recentFileMessageWritten:
                    print ('wrote recent file: %s' % fileName)
                    written = True
                self.writeRecentFilesFileHelper(fileName)
                # Bug fix: Leo 4.4.6: write *all* recent files.

    if written:
        self.recentFileMessageWritten = True
    else:
        pass # g.trace('----- not found: %s' % g.os_path_join(localPath,tag))
#@+node:ekr.20050424131051:writeRecentFilesFileHelper
def writeRecentFilesFileHelper (self,fileName):
    # g.trace(fileName)

    # Don't update the file if it begins with read-only.
    theFile = None
    try:
        theFile = file(fileName)
        lines = theFile.readlines()
        if lines and self.munge(lines[0])=='readonly':
            # g.trace('read-only: %s' %fileName)
            return
    except IOError:
        # The user may have erased a file.  Not an error.
        if theFile: theFile.close()

    theFile = None
    try:
        # g.trace('writing',fileName)
        theFile = file(fileName,'w')
        if self.recentFiles:
            lines = [g.toEncodedString(line,'utf-8') for line in self.recentFiles]
            theFile.write('\n'.join(lines))
            # g.trace(fileName,'lines\n%s' % lines)
        else:
            theFile.write('\n')

    except IOError:
        # The user may have erased a file.  Not an error.
        pass

    except Exception:
        g.es('unexpected exception writing',fileName,color='red')
        g.es_exception()

    if theFile:
        theFile.close()
#@-node:ekr.20050424131051:writeRecentFilesFileHelper
#@-node:ekr.20050424114937.2:writeRecentFilesFile & helper
#@-node:ekr.20050424114937.1:Reading and writing .leoRecentFiles.txt (g.app.config)
#@-node:ekr.20071213095517:Fixed bug with clear-recent-files
#@+node:ekr.20071213095537:Fixed bug: replace didn't always start in the correct place
@nocolor

This was a very long-standing bug: the most annoying in all of Leo.

The problem was an extra call to findNextCommand in generalChangeHelper (!)

I added much better tracing code in the base leoFind class,
controled by the trace ivar.

@color
#@nonl
#@+node:ekr.20061212084717:class leoFind
class leoFind:

    """The base class for Leo's Find commands."""

    @others
#@+node:ekr.20031218072017.3053:leoFind.__init__ & helpers
def __init__ (self,c,title=None):

    self.c = c
    self.trace = False

    # g.trace('leoFind',c)

    # Spell checkers use this class, so we can't always compute a title.
    if title:
        self.title = title
    else:
        << compute self.title >>

    << init the gui-independent ivars >>

def init (self,c):
    self.oops()
#@+node:ekr.20041121145452:<< compute self.title >>
if not c.mFileName:
    s = "untitled"
else:
    path,s = g.os_path_split(c.mFileName)

self.title = "Find/Change for %s" %  s
#@-node:ekr.20041121145452:<< compute self.title >>
#@+node:ekr.20031218072017.3054:<< init the gui-independent ivars >>
self.wrapPosition = None
self.onlyPosition = None
self.find_text = ""
self.change_text = ""
self.unstick = False
self.re_obj = None

@
New in 4.3:
- These are the names of leoFind ivars. (no more _flag hack).
- There are no corresponding commander ivars to keep in synch (hurray!)
- These ivars are inited (in the subclass by init) when this class is created.
- These ivars are updated by update_ivars just before doing any find.
@c

<< do dummy initialization to keep Pychecker happy >>

self.intKeys = [
    "batch","ignore_case", "node_only",
    "pattern_match", "search_headline", "search_body",
    "suboutline_only", "mark_changes", "mark_finds", "reverse",
    "script_search","script_change","selection_only",
    "wrap", "whole_word",
]

self.newStringKeys = ["radio-find-type", "radio-search-scope"]

# Ivars containing internal state...
self.c = None # The commander for this search.
self.clone_find_all = False
self.p = None # The position being searched.  Never saved between searches!
self.in_headline = False # True: searching headline text.
self.s_ctrl = searchWidget() # The search text for this search.
self.wrapping = False # True: wrapping is enabled.
    # This is _not_ the same as self.wrap for batch searches.

@ Initializing a wrapped search is tricky.  The search() method will fail if p==wrapPosition and pos >= wrapPos.  selectNextPosition() will fail if p == wrapPosition.  We set wrapPos on entry, before the first search.  We set wrapPosition in selectNextPosition after the first search fails.  We also set wrapPosition on exit if the first search suceeds.
@c

self.wrapPosition = None # The start of wrapped searches: persists between calls.
self.onlyPosition = None # The starting node for suboutline-only searches.
self.wrapPos = None # The starting position of the wrapped search: persists between calls.
self.errors = 0
#@+node:ekr.20050123164539:<< do dummy initialization to keep Pychecker happy >>
if 1:
    self.batch = None
    self.clone_find_all = None
    self.ignore_case = None
    self.node_only = None
    self.pattern_match = None
    self.search_headline = None
    self.search_body = None
    self.suboutline_only = None
    self.mark_changes = None
    self.mark_finds = None
    self.reverse = None
    self.script_search = None
    self.script_change = None
    self.wrap = None
    self.whole_word = None

if 1:
    self.change_ctrl = None
    self.find_ctrl = None
    self.frame = None
    self.svarDict = {}
#@-node:ekr.20050123164539:<< do dummy initialization to keep Pychecker happy >>
#@-node:ekr.20031218072017.3054:<< init the gui-independent ivars >>
#@-node:ekr.20031218072017.3053:leoFind.__init__ & helpers
#@+node:ekr.20060123065756.1:Top Level Buttons
#@+node:ekr.20031218072017.3057:changeAllButton
# The user has pushed the "Change All" button from the find panel.

def changeAllButton(self):

    c = self.c
    self.setup_button()
    c.clearAllVisited() # Clear visited for context reporting.

    if self.script_change:
        self.doChangeAllScript()
    else:
        self.changeAll()
#@-node:ekr.20031218072017.3057:changeAllButton
#@+node:ekr.20031218072017.3056:changeButton
# The user has pushed the "Change" button from the find panel.

def changeButton(self):

    self.setup_button()

    if self.script_change:
        self.doChangeScript()
    else:
        self.change()
#@-node:ekr.20031218072017.3056:changeButton
#@+node:ekr.20031218072017.3058:changeThenFindButton
# The user has pushed the "Change Then Find" button from the find panel.

def changeThenFindButton(self):

    self.setup_button()

    if self.script_change:
        self.doChangeScript()
        if self.script_search:
            self.doFindScript()
        else:
            self.findNext()
    else:
        if self.script_search:
            self.change()
            self.doFindScript()
        else:
            self.changeThenFind()
#@-node:ekr.20031218072017.3058:changeThenFindButton
#@+node:ekr.20031218072017.3060:findAllButton
# The user has pushed the "Find All" button from the find panel.

def findAllButton(self):

    c = self.c
    self.setup_button()
    c.clearAllVisited() # Clear visited for context reporting.

    if self.script_search:
        self.doFindAllScript()
    else:
        self.findAll()
#@-node:ekr.20031218072017.3060:findAllButton
#@+node:ekr.20031218072017.3059:findButton
# The user has pushed the "Find" button from the find panel.

def findButton(self):

    self.setup_button()

    if self.script_search:
        self.doFindScript()
    else:
        self.findNext()
#@-node:ekr.20031218072017.3059:findButton
#@+node:ekr.20031218072017.3065:setup_button
# Initializes a search when a button is pressed in the Find panel.

def setup_button(self):

    c = self.c
    self.p = c.currentPosition()

    c.bringToFront()
    if 0: # We _must_ retain the editing status for incremental searches!
        c.endEditing()

    self.update_ivars()
#@-node:ekr.20031218072017.3065:setup_button
#@-node:ekr.20060123065756.1:Top Level Buttons
#@+node:ekr.20031218072017.3055:Top Level Commands
#@+node:ekr.20031218072017.3061:changeCommand
# The user has selected the "Replace" menu item.

def changeCommand(self,c):

    self.setup_command()

    if self.script_search:
        self.doChangeScript()
    else:
        self.change()
#@-node:ekr.20031218072017.3061:changeCommand
#@+node:ekr.20031218072017.3062:changeThenFindCommand
# The user has pushed the "Change Then Find" button from the Find menu.

def changeThenFindCommand(self,c):

    self.setup_command()

    if self.script_search:
        self.doChangeScript()
        self.doFindScript()
    else:
        self.changeThenFind()
#@-node:ekr.20031218072017.3062:changeThenFindCommand
#@+node:ekr.20051013084200.1:dismiss: defined in subclass class
def dismiss (self):
    pass
#@-node:ekr.20051013084200.1:dismiss: defined in subclass class
#@+node:ekr.20031218072017.3063:findNextCommand
# The user has selected the "Find Next" menu item.

def findNextCommand(self,c):

    self.setup_command()

    if self.script_search:
        self.doFindScript()
    else:
        self.findNext()
#@-node:ekr.20031218072017.3063:findNextCommand
#@+node:ekr.20031218072017.3064:findPreviousCommand
# The user has selected the "Find Previous" menu item.

def findPreviousCommand(self,c):

    self.setup_command()

    self.reverse = not self.reverse

    if self.script_search:
        self.doFindScript()
    else:
        self.findNext()

    self.reverse = not self.reverse
#@-node:ekr.20031218072017.3064:findPreviousCommand
#@+node:EKR.20040503070514:handleUserClick
def handleUserClick (self,p):

    """Reset suboutline-only search when the user clicks a headline."""

    try:
        if self.c and self.suboutline_only:
            # g.trace(p)
            self.onlyPosition = p.copy()
    except: pass
#@-node:EKR.20040503070514:handleUserClick
#@+node:ekr.20031218072017.3066:setup_command
# Initializes a search when a command is invoked from the menu.

def setup_command(self):

    # g.trace('leoFind')

    if 0: # We _must_ retain the editing status for incremental searches!
        self.c.endEditing()

    self.update_ivars()
#@-node:ekr.20031218072017.3066:setup_command
#@-node:ekr.20031218072017.3055:Top Level Commands
#@+node:ekr.20031218072017.3067:Find/change utils
#@+node:ekr.20031218072017.2293:batchChange (sets start of change-all group)
@ This routine performs a single batch change operation, updating the head or body string of p and leaving the result in s_ctrl.  We update the body if we are changing the body text of c.currentVnode().

s_ctrl contains the found text on entry and contains the changed text on exit.  pos and pos2 indicate the selection.  The selection will never be empty. NB: we can not assume that self.p is visible.
@c

def batchChange (self,pos1,pos2):

    c = self.c ; u = c.undoer
    p = self.p ; w = self.s_ctrl
    # Replace the selection with self.change_text
    if pos1 > pos2: pos1,pos2=pos2,pos1
    s = w.getAllText()
    if pos1 != pos2: w.delete(pos1,pos2)
    w.insert(pos1,self.change_text)
    # Update the selection.
    insert=g.choose(self.reverse,pos1,pos1+len(self.change_text))
    w.setSelectionRange(insert,insert)
    w.setInsertPoint(insert)
    # Update the node
    s = w.getAllText() # Used below.
    if self.in_headline:
        << change headline >>
    else:
        << change body >>
#@+node:ekr.20031218072017.2294:<< change headline >>
if len(s) > 0 and s[-1]=='\n': s = s[:-1]

if s != p.headString():

    undoData = u.beforeChangeNodeContents(p)

    p.initHeadString(s)
    if self.mark_changes:
        p.setMarked()
    p.setDirty()
    if not c.isChanged():
        c.setChanged(True)

    u.afterChangeNodeContents(p,'Change Headline',undoData)
#@-node:ekr.20031218072017.2294:<< change headline >>
#@+node:ekr.20031218072017.2295:<< change body >>
if len(s) > 0 and s[-1]=='\n': s = s[:-1]

if s != p.bodyString():

    undoData = u.beforeChangeNodeContents(p)

    c.setBodyString(p,s)
    if self.mark_changes:
        p.setMarked()
    p.setDirty()
    if not c.isChanged():
        c.setChanged(True)

    u.afterChangeNodeContents(p,'Change Body',undoData)
#@-node:ekr.20031218072017.2295:<< change body >>
#@-node:ekr.20031218072017.2293:batchChange (sets start of change-all group)
#@+node:ekr.20031218072017.3068:change
def change(self,event=None):

    if self.checkArgs():
        self.initInHeadline()
        self.changeSelection()
#@-node:ekr.20031218072017.3068:change
#@+node:ekr.20031218072017.3069:changeAll
def changeAll(self):

    # g.trace(g.callers())

    c = self.c ; u = c.undoer ; undoType = 'Change All'
    current = c.currentPosition()
    w = self.s_ctrl
    if not self.checkArgs(): return
    self.initInHeadline()
    saveData = self.save()
    self.initBatchCommands()
    count = 0
    c.beginUpdate()
    try: # In update...
        u.beforeChangeGroup(current,undoType)
        while 1:
            pos1, pos2 = self.findNextMatch()
            if pos1 is None: break
            count += 1
            self.batchChange(pos1,pos2)
            s = w.getAllText()
            i,j = g.getLine(s,pos1)
            line = s[i:j]
            # self.printLine(line,allFlag=True)
        p = c.currentPosition()
        u.afterChangeGroup(p,undoType,reportFlag=True)
        g.es("changed:",count,"instances")
    finally:
        c.endUpdate()
        self.restore(saveData)
#@-node:ekr.20031218072017.3069:changeAll
#@+node:ekr.20031218072017.3070:changeSelection
# Replace selection with self.change_text.
# If no selection, insert self.change_text at the cursor.

def changeSelection(self):

    c = self.c ; p = self.p
    w = g.choose(self.in_headline,c.edit_widget(p),c.frame.body.bodyCtrl) # 2007:10/25
    oldSel = sel = w.getSelectionRange()
    start,end = sel
    if start > end: start,end = end,start
    if start == end:
        g.es("no text selected") ; return False

    # g.trace(start,end)

    # Replace the selection in _both_ controls.
    start,end = oldSel
    change_text = self.change_text

    # Perform regex substitutions of \1, \2, ...\9 in the change text.
    if self.pattern_match and self.match_obj:
        groups = self.match_obj.groups()
        if groups:
            change_text = self.makeRegexSubs(change_text,groups)
    # change_text = change_text.replace('\\n','\n').replace('\\t','\t')
    change_text = self.replaceBackSlashes(change_text)

    for w2 in (w,self.s_ctrl):
        if start != end: w2.delete(start,end)
        w2.insert(start,change_text)
        w2.setInsertPoint(g.choose(self.reverse,start,start+len(change_text)))

    # Update the selection for the next match.
    w.setSelectionRange(start,start+len(change_text))
    c.widgetWantsFocus(w)

    # No redraws here: they would destroy the headline selection.
    c.beginUpdate()
    try:
        if self.mark_changes:
            p.setMarked()
        if self.in_headline:
            c.frame.tree.onHeadChanged(p,'Change')
        else:
            c.frame.body.onBodyChanged('Change',oldSel=oldSel)
    finally:
        c.endUpdate(False)
        c.frame.tree.drawIcon(p) # redraw only the icon.

    return True
#@+node:ekr.20060526201951:makeRegexSubs
def makeRegexSubs(self,s,groups):

    '''Carefully substitute group[i-1] for \i strings in s.
    The group strings may contain \i strings: they are *not* substituted.'''

    digits = '123456789'
    result = [] ; n = len(s)
    i = j = 0 # s[i:j] is the text between \i markers.
    while j < n:
        k = s.find('\\',j)
        if k == -1 or k + 1 >= n:
            break
        j = k + 1 ; ch = s[j]
        if ch in digits:
            j += 1
            result.append(s[i:k]) # Append up to \i
            i = j
            gn = int(ch)-1
            if gn < len(groups):
                result.append(groups[gn]) # Append groups[i-1]
            else:
                result.append('\\%s' % ch) # Append raw '\i'
    result.append(s[i:])
    return ''.join(result)
#@-node:ekr.20060526201951:makeRegexSubs
#@-node:ekr.20031218072017.3070:changeSelection
#@+node:ekr.20031218072017.3071:changeThenFind
def changeThenFind(self):

    if not self.checkArgs():
        return

    self.initInHeadline()
    if self.changeSelection():
        self.findNext(False) # don't reinitialize
#@-node:ekr.20031218072017.3071:changeThenFind
#@+node:ekr.20031218072017.2417:doChange...Script
def doChangeScript (self):

    g.app.searchDict["type"] = "change"
    self.runChangeScript()

def doChangeAllScript (self):

    """The user has just pressed the Change All button with script-change box checked.

    N.B. Only this code is executed."""

    g.app.searchDict["type"] = "changeAll"
    while 1:
        self.runChangeScript()
        if not g.app.searchDict.get("continue"):
            break

def runChangeScript (self):

    try:
        assert(self.script_change)
        exec self.change_text in {} # Use {} to get a pristine environment.
    except:
        g.es("exception executing change script")
        g.es_exception(full=False)
        g.app.searchDict["continue"] = False # 2/1/04
#@-node:ekr.20031218072017.2417:doChange...Script
#@+node:ekr.20031218072017.3072:doFind...Script
def doFindScript (self):

    g.app.searchDict["type"] = "find"
    self.runFindScript()

def doFindAllScript (self):

    """The user has just pressed the Find All button with script-find radio button checked.

    N.B. Only this code is executed."""

    g.app.searchDict["type"] = "findAll"
    while 1:
        self.runFindScript()
        if not g.app.searchDict.get("continue"):
            break

def runFindScript (self):

    try:
        exec self.find_text in {} # Use {} to get a pristine environment.
    except:
        g.es("exception executing find script")
        g.es_exception(full=False)
        g.app.searchDict["continue"] = False # 2/1/04
#@-node:ekr.20031218072017.3072:doFind...Script
#@+node:ekr.20031218072017.3073:findAll
def findAll(self):

    c = self.c ; w = self.s_ctrl ; u = c.undoer
    undoType = 'Clone Find All'
    if not self.checkArgs():
        return
    self.initInHeadline()
    data = self.save()
    self.initBatchCommands()
    count = 0 ; clones = []
    while 1:
        pos, newpos = self.findNextMatch()
        if pos is None: break
        count += 1
        s = w.getAllText()
        i,j = g.getLine(s,pos)
        line = s[i:j]
        if not self.clone_find_all:
            self.printLine(line,allFlag=True)
        if self.clone_find_all and self.p.v.t not in clones:
            # g.trace(self.p.v.t,self.p.headString())
            if not clones:
                << create the found node and begin the undo group >>
            clones.append(self.p.v.t)
            << create a clone of p under the find node >>
    if self.clone_find_all and clones:
        c.setRootPosition(c.findRootPosition(found)) # New in 4.4.2.
        u.afterChangeGroup(found,undoType,reportFlag=True) 
        c.selectPosition(found) # Recomputes root.
        c.setChanged(True)

    c.redraw_now()
    g.es("found",count,"matches")
    self.restore(data)
#@+node:ekr.20051113110735:<< create the found node and begin the undo group >>
u.beforeChangeGroup(c.currentPosition(),undoType)

undoData = u.beforeInsertNode(c.currentPosition())

oldRoot = c.rootPosition()
found = oldRoot.insertAfter()
found.moveToRoot(oldRoot)
c.setHeadString(found,'Found: ' + self.find_text)

u.afterInsertNode(found,undoType,undoData,dirtyVnodeList=[])
#@-node:ekr.20051113110735:<< create the found node and begin the undo group >>
#@+node:ekr.20051113110851:<< create a clone of p under the find node >>
undoData = u.beforeCloneNode(self.p)
q = self.p.clone()
q.moveToLastChildOf(found)
u.afterCloneNode(q,undoType,undoData,dirtyVnodeList=[])
#@-node:ekr.20051113110851:<< create a clone of p under the find node >>
#@-node:ekr.20031218072017.3073:findAll
#@+node:ekr.20031218072017.3074:findNext
def findNext(self,initFlag=True):

    c = self.c
    if not self.checkArgs():
        return

    if initFlag:
        self.initInHeadline()
        data = self.save()
        self.initInteractiveCommands()
    else:
        data = self.save()

    pos, newpos = self.findNextMatch()

    if pos is None:
        if self.wrapping:
            g.es("end of wrapped search")
        else:
            g.es("not found","'%s'" % (self.find_text))
        self.restore(data)
    else:
        self.showSuccess(pos,newpos)
#@-node:ekr.20031218072017.3074:findNext
#@+node:ekr.20031218072017.3075:findNextMatch
# Resumes the search where it left off.
# The caller must call set_first_incremental_search or set_first_batch_search.

def findNextMatch(self):

    c = self.c ; trace = self.trace

    if trace: g.trace('entry',g.callers())

    if not self.search_headline and not self.search_body:
        if trace: g.trace('nothing to search')
        return None, None

    if len(self.find_text) == 0:
        if trace: g.trace('no find text')
        return None, None

    p = self.p ; self.errors = 0
    attempts = 0
    self.backwardAttempts = 0

    # New in Leo 4.4.8: precompute the regexp for regexHelper.
    if self.pattern_match:
        try: # Precompile the regexp.
            flags = re.MULTILINE
            if self.ignore_case: flags |= re.IGNORECASE
            self.re_obj = re.compile(self.find_text,flags)
        except Exception:
            g.es('invalid regular expression:',pattern,color='blue')
            self.errors += 1 # Abort the search.
            return None,None

    while p:
        pos, newpos = self.search()
        if trace: g.trace('attempt','pos',pos,'p',p.headString())
        if pos is not None:
            if self.mark_finds:
                p.setMarked()
                c.frame.tree.drawIcon(p) # redraw only the icon.
            if trace: g.trace('success',pos,newpos)
            return pos, newpos
        elif self.errors:
            g.trace('find errors')
            return None,None # Abort the search.
        elif self.node_only:
            if trace: g.trace('fail: node only')
            return None,None # We are only searching one node.
        else:
            if trace: g.trace('failed attempt',p)
            attempts += 1
            p = self.p = self.selectNextPosition()

    if trace: g.trace('attempts',attempts,'backwardAttempts',self.backwardAttempts)
    return None, None
#@-node:ekr.20031218072017.3075:findNextMatch
#@+node:ekr.20031218072017.3076:resetWrap
def resetWrap (self,event=None):

    self.wrapPosition = None
    self.onlyPosition = None
#@-node:ekr.20031218072017.3076:resetWrap
#@+node:ekr.20031218072017.3077:search & helpers
def search (self):

    """Search s_ctrl for self.find_text under the control of the
    whole_word, ignore_case, and pattern_match ivars.

    Returns (pos, newpos) or (None,None)."""

    c = self.c ; p = self.p ; w = self.s_ctrl ; trace = self.trace
    index = w.getInsertPoint()
    s = w.getAllText()

    # g.trace(index,repr(s[index:index+20]))
    stopindex = g.choose(self.reverse,0,len(s)) # 'end' doesn't work here.
    pos,newpos = self.searchHelper(s,index,stopindex,self.find_text,
        backwards=self.reverse,nocase=self.ignore_case,
        regexp=self.pattern_match,word=self.whole_word)

    # g.trace('pos,newpos',pos,newpos)
    if pos == -1:
        if trace: g.trace('** pos is -1',pos,newpos)
        return None,None
    << fail if we are passed the wrap point >>
    insert = g.choose(self.reverse,min(pos,newpos),max(pos,newpos))
    w.setSelectionRange(pos,newpos,insert=insert)

    if trace: g.trace('** returns',pos,newpos)
    return pos,newpos
#@+node:ekr.20060526140328:<< fail if we are passed the wrap point >>
if self.wrapping and self.wrapPos is not None and self.wrapPosition and p == self.wrapPosition:

    if self.reverse and pos < self.wrapPos:
        if trace: g.trace("** reverse wrap done",pos,newpos)
        return None, None

    if not self.reverse and newpos > self.wrapPos:
        if trace: g.trace('** wrap done',pos,newpos)
        return None, None
#@-node:ekr.20060526140328:<< fail if we are passed the wrap point >>
#@+node:ekr.20060526081931:searchHelper & allies
def searchHelper (self,s,i,j,pattern,backwards,nocase,regexp,word,swapij=True):

    trace = self.trace

    if swapij and backwards: i,j = j,i

    if trace: g.trace('back,nocase,regexp,word,',
        backwards,nocase,regexp,word,i,j,repr(s[i:i+20]))

    if not s[i:j] or not pattern:
        if trace: g.trace('empty',i,j,'len(s)',len(s),'pattern',pattern)
        return -1,-1

    if regexp:
        pos,newpos = self.regexHelper(s,i,j,pattern,backwards,nocase)
    elif backwards:
        pos,newpos = self.backwardsHelper(s,i,j,pattern,nocase,word)
    else:
        pos,newpos = self.plainHelper(s,i,j,pattern,nocase,word)

    if trace: g.trace('returns',pos,newpos)
    return pos,newpos
#@+node:ekr.20060526092203:regexHelper
def regexHelper (self,s,i,j,pattern,backwards,nocase):

    re_obj = self.re_obj # Use the pre-compiled object
    if not re_obj:
        g.trace('can not happen: no re_obj')
        return -1,-1

    # try:
        # flags = re.MULTILINE
        # if nocase: flags |= re.IGNORECASE
        # re_obj = re.compile(pattern,flags)
    # except Exception:
        # g.es('invalid regular expression:',pattern,color='blue')
        # self.errors += 1 # Abort the search.
        # return -1, -1

    if backwards: # Scan to the last match.  We must use search here.
        last_mo = None ; i = 0
        while i < len(s):
            mo = re_obj.search(s,i,j)
            if not mo: break
            i += 1 ; last_mo = mo
        mo = last_mo
    else:
        mo = re_obj.search(s,i,j)

    if 0:
        g.trace('i',i,'j',j,'s[i:j]',repr(s[i:j]),
            'mo.start',mo and mo.start(),'mo.end',mo and mo.end())

    while mo and 0 <= i < len(s):
        if mo.start() == mo.end():
            if backwards:
                # Search backward using match instead of search.
                i -= 1
                while 0 <= i < len(s):
                    mo = re_obj.match(s,i,j)
                    if mo: break
                    i -= 1
            else:
                i += 1 ; mo = re_obj.search(s,i,j)
        else:
            self.match_obj = mo
            return mo.start(),mo.end()
    self.match_obj = None
    return -1,-1
#@-node:ekr.20060526092203:regexHelper
#@+node:ekr.20060526140744:backwardsHelper
debugIndices = []

@
rfind(sub [,start [,end]])

Return the highest index in the string where substring sub is found, such that
sub is contained within s[start,end]. Optional arguments start and end are
interpreted as in slice notation. Return -1 on failure.
@c

def backwardsHelper (self,s,i,j,pattern,nocase,word):

    debug = False
    if nocase:
        s = s.lower() ; pattern = pattern.lower() # Bug fix: 10/5/06: At last the bug is found!
    pattern = self.replaceBackSlashes(pattern)
    n = len(pattern)

    if i < 0 or i > len(s) or j < 0 or j > len(s):
        g.trace('bad index: i = %s, j = %s' % (i,j))
        i = 0 ; j = len(s)

    if debug and (s and i == 0 and j == 0):
        g.trace('two zero indices')

    self.backwardAttempts += 1

    # short circuit the search: helps debugging.
    if s.find(pattern) == -1:
        if debug:
            self.debugCount += 1
            if self.debugCount < 50:
                g.trace(i,j,'len(s)',len(s),self.p.headString())
        return -1,-1

    if word:
        while 1:
            k = s.rfind(pattern,i,j)
            if debug: g.trace('**word** %3s %3s %5s -> %s %s' % (i,j,g.choose(j==len(s),'(end)',''),k,self.p.headString()))
            if k == -1: return -1, -1
            if self.matchWord(s,k,pattern):
                return k,k+n
            else:
                j = max(0,k-1)
    else:
        k = s.rfind(pattern,i,j)
        if debug: g.trace('%3s %3s %5s -> %s %s' % (i,j,g.choose(j==len(s),'(end)',''),k,self.p.headString()))
        if k == -1:
            return -1, -1
        else:
            return k,k+n
#@-node:ekr.20060526140744:backwardsHelper
#@+node:ekr.20060526093531:plainHelper
def plainHelper (self,s,i,j,pattern,nocase,word):

    trace = self.trace

    # if trace: g.trace(i,j,repr(s[i:i+20]),'pattern',repr(pattern),'word',repr(word))
    if trace: g.trace(i,j,repr(s[i:i+20]))

    if nocase:
        s = s.lower() ; pattern = pattern.lower()
    pattern = self.replaceBackSlashes(pattern)
    n = len(pattern)
    if word:
        while 1:
            k = s.find(pattern,i,j)
            # g.trace(k,n)
            if k == -1:
                if trace: g.trace('no match word',i)
                return -1, -1
            elif self.matchWord(s,k,pattern):
                if trace: g.trace('match word',k)
                return k, k + n
            else: i = k + n
    else:
        k = s.find(pattern,i,j)
        if k == -1:
            if trace: g.trace('no match word',i)
            return -1, -1
        else:
            if trace: g.trace('match', k)
            return k, k + n
#@-node:ekr.20060526093531:plainHelper
#@+node:ekr.20060526140744.1:matchWord
def matchWord(self,s,i,pattern):

    trace = self.trace

    pattern = self.replaceBackSlashes(pattern)
    if not s or not pattern or not g.match(s,i,pattern):
        if trace: g.trace('empty')
        return False

    pat1,pat2 = pattern[0],pattern[-1]
    # n = self.patternLen(pattern)
    n = len(pattern)
    ch1 = 0 <= i-1 < len(s) and s[i-1] or '.'
    ch2 = 0 <= i+n < len(s) and s[i+n] or '.'

    isWordPat1 = g.isWordChar(pat1)
    isWordPat2 = g.isWordChar(pat2)
    isWordCh1 = g.isWordChar(ch1)
    isWordCh2 = g.isWordChar(ch2)

    # g.trace('i',i,'ch1,ch2,pat',repr(ch1),repr(ch2),repr(pattern))

    inWord = isWordPat1 and isWordCh1 or isWordPat2 and isWordCh2
    if trace: g.trace('returns',not inWord)
    return not inWord

#@-node:ekr.20060526140744.1:matchWord
#@+node:ekr.20070105165924:replaceBackSlashes
def replaceBackSlashes (self,s):

    '''Carefully replace backslashes in a search pattern.'''

    # This is NOT the same as s.replace('\\n','\n').replace('\\t','\t').replace('\\\\','\\')
    # because there is no rescanning.

    i = 0
    while i + 1 < len(s):
        if s[i] == '\\':
            ch = s[i+1]
            if ch == '\\':
                s = s[:i] + s[i+1:] # replace \\ by \
            elif ch == 'n':
                s = s[:i] + '\n' + s[i+2:] # replace the \n by a newline
            elif ch == 't':
                s = s[:i] + '\t' + s[i+2:] # replace \t by a tab
            else:
                i += 1 # Skip the escaped character.
        i += 1

    if self.trace: g.trace(repr(s))
    return s
#@-node:ekr.20070105165924:replaceBackSlashes
#@-node:ekr.20060526081931:searchHelper & allies
#@-node:ekr.20031218072017.3077:search & helpers
#@+node:ekr.20031218072017.3081:selectNextPosition
# Selects the next node to be searched.

def selectNextPosition(self):

    c = self.c ; p = self.p ; trace = False

    # Start suboutline only searches.
    if self.suboutline_only and not self.onlyPosition:
        # p.copy not needed because the find code never calls p.moveToX.
        # Furthermore, p might be None, so p.copy() would be wrong!
        self.onlyPosition = p 

    # Start wrapped searches.
    if self.wrapping and not self.wrapPosition:
        assert(self.wrapPos != None)
        # p.copy not needed because the find code never calls p.moveToX.
        # Furthermore, p might be None, so p.copy() would be wrong!
        self.wrapPosition = p 

    if self.in_headline and self.search_body:
        # just switch to body pane.
        self.in_headline = False
        self.initNextText()
        if trace: g.trace('switching to body',g.callers(5))
        return p

    if self.reverse: p = p.threadBack()
    else:            p = p.threadNext()

    # if trace: g.trace(p and p.headString() or 'None')

    # New in 4.3: restrict searches to hoisted area.
    # End searches outside hoisted area.
    if c.hoistStack:
        if not p:
            if self.wrapping:
                g.es('wrap disabled in hoisted outlines',color='blue')
            return
        bunch = c.hoistStack[-1]
        if not bunch.p.isAncestorOf(p):
            g.es('found match outside of hoisted outline',color='blue')
            return None

    # Wrap if needed.
    if not p and self.wrapping and not self.suboutline_only:
        p = c.rootPosition()
        if self.reverse:
            # Set search_v to the last node of the tree.
            while p and p.next():
                p = p.next()
            if p: p = p.lastNode()

    # End wrapped searches.
    if self.wrapping and p and p == self.wrapPosition:
        if trace: g.trace("ending wrapped search")
        p = None ; self.resetWrap()

    # End suboutline only searches.
    if (self.suboutline_only and self.onlyPosition and p and
        (p == self.onlyPosition or not self.onlyPosition.isAncestorOf(p))):
        # g.trace("end outline-only")
        p = None ; self.onlyPosition = None

    # p.copy not needed because the find code never calls p.moveToX.
    # Furthermore, p might be None, so p.copy() would be wrong!
    self.p = p # used in initNextText().
    if p: # select p and set the search point within p.
        self.in_headline = self.search_headline
        self.initNextText()
    return p
#@-node:ekr.20031218072017.3081:selectNextPosition
#@-node:ekr.20031218072017.3067:Find/change utils
#@+node:ekr.20061212095134.1:General utils
#@+node:ekr.20051020120306.26:bringToFront (leoFind)
def bringToFront (self):

    """Bring the Find Tab to the front and select the entire find text."""

    c = self.c ; w = self.find_ctrl

    # g.trace(g.callers())
    c.widgetWantsFocusNow(w)
    g.app.gui.selectAllText(w)
    c.widgetWantsFocus(w)
#@-node:ekr.20051020120306.26:bringToFront (leoFind)
#@+node:ekr.20061111084423.1:oops (leoFind)
def oops(self):
    print ("leoFind oops:",
        g.callers(10),"should be overridden in subclass")
#@-node:ekr.20061111084423.1:oops (leoFind)
#@+node:ekr.20051020120306.27:selectAllFindText (leoFind)
def selectAllFindText (self,event=None):

    # __pychecker__ = '--no-argsused' # event

    # This is called only when the user presses ctrl-a in the find panel.

    w = self.frame.focus_get()
    if g.app.gui.isTextWidget(w):
        w.selectAllText()

    return "break"
#@-node:ekr.20051020120306.27:selectAllFindText (leoFind)
#@-node:ekr.20061212095134.1:General utils
#@+node:ekr.20031218072017.3082:Initing & finalizing
#@+node:ekr.20031218072017.3083:checkArgs
def checkArgs (self):

    val = True
    if not self.search_headline and not self.search_body:
        g.es("not searching headline or body")
        val = False
    if len(self.find_text) == 0:
        g.es("empty find patttern")
        val = False
    return val
#@-node:ekr.20031218072017.3083:checkArgs
#@+node:ekr.20031218072017.3084:initBatchCommands
# Initializes for the Find All and Change All commands.

def initBatchCommands (self):

    c = self.c
    self.in_headline = self.search_headline # Search headlines first.
    self.errors = 0

    # Select the first node.
    if self.suboutline_only or self.node_only:
        self.p = c.currentPosition()
    else:
        p = c.rootPosition()
        if self.reverse:
            while p and p.next():
                p = p.next()
            p = p.lastNode()
        self.p = p

    # Set the insert point.
    self.initBatchText()
#@-node:ekr.20031218072017.3084:initBatchCommands
#@+node:ekr.20031218072017.3085:initBatchText, initNextText & init_s_ctrl
# Returns s_ctrl with "insert" point set properly for batch searches.
def initBatchText(self,ins=None):
    p = self.p
    self.wrapping = False # Only interactive commands allow wrapping.
    s = g.choose(self.in_headline,p.headString(), p.bodyString())
    self.init_s_ctrl(s,ins)

# Call this routine when moving to the next node when a search fails.
# Same as above except we don't reset wrapping flag.
def initNextText(self,ins=None):
    p = self.p
    s = g.choose(self.in_headline,p.headString(), p.bodyString())
    self.init_s_ctrl(s,ins)

def init_s_ctrl (self,s,ins):

    w = self.s_ctrl
    w.setAllText(s)
    if ins is None:
        ins = g.choose(self.reverse,len(s),0)
        # print g.choose(self.reverse,'.','*'),
    else:
        pass # g.trace('ins',ins)
    w.setInsertPoint(ins)
#@-node:ekr.20031218072017.3085:initBatchText, initNextText & init_s_ctrl
#@+node:ekr.20031218072017.3086:initInHeadline
# Guesses which pane to start in for incremental searches and changes.
# This must not alter the current "insert" or "sel" marks.

def initInHeadline (self):

    c = self.c ; p = self.p

    # Do not change this without careful thought and extensive testing!
    if self.search_headline and self.search_body:
        # A temporary expedient.
        if self.reverse:
            self.in_headline = False
        else:
            # Search headline first.
            self.in_headline = (
                p == c.frame.tree.editPosition() and
                c.get_focus() != c.frame.body.bodyCtrl)
    else:
        self.in_headline = self.search_headline
#@-node:ekr.20031218072017.3086:initInHeadline
#@+node:ekr.20031218072017.3087:initInteractiveCommands
def initInteractiveCommands(self):

    c = self.c ; p = self.p
    w = g.choose(self.in_headline,c.edit_widget(p),c.frame.body.bodyCtrl)
    self.errors = 0

    # We only use the insert point, *never* the selection range.
    ins = w.getInsertPoint()
    # g.trace('ins',ins)
    self.debugCount = 0
    self.initNextText(ins=ins)
    c.widgetWantsFocus(w)

    self.wrapping = self.wrap
    if self.wrap and self.wrapPosition == None:
        self.wrapPos = ins
        # Do not set self.wrapPosition here: that must be done after the first search.
#@-node:ekr.20031218072017.3087:initInteractiveCommands
#@+node:ekr.20031218072017.3088:printLine
def printLine (self,line,allFlag=False):

    both = self.search_body and self.search_headline
    context = self.batch # "batch" now indicates context

    if allFlag and both and context:
        g.es('','-' * 20,'',self.p.headString())
        theType = g.choose(self.in_headline,"head: ","body: ")
        g.es('',theType + line)
    elif allFlag and context and not self.p.isVisited():
        # We only need to print the context once.
        g.es('','-' * 20,'',self.p.headString())
        g.es('',line)
        self.p.setVisited()
    else:
        g.es('',line)
#@-node:ekr.20031218072017.3088:printLine
#@+node:ekr.20031218072017.3089:restore
# Restores the screen after a search fails

def restore (self,data):

    c = self.c
    in_headline,p,t,insert,start,end = data

    c.frame.bringToFront() # Needed on the Mac

    # Don't try to reedit headline.
    c.selectPosition(p)

    if not in_headline:
        # Looks good and provides clear indication of failure or termination.
        t.setSelectionRange(insert,insert)
        t.setInsertPoint(insert)
        t.seeInsertPoint()

    #g.trace(c.widget_name(t))

    if 1: # I prefer always putting the focus in the body.
        c.invalidateFocus()
        c.bodyWantsFocusNow()
    else:
        c.widgetWantsFocusNow(t)
#@-node:ekr.20031218072017.3089:restore
#@+node:ekr.20031218072017.3090:save
def save (self):

    c = self.c ; p = self.p
    w = g.choose(self.in_headline,c.edit_widget(p),c.frame.body.bodyCtrl)

    # 2007/10/24: defensive programming for unit tests.
    if w:
        insert = w.getInsertPoint()
        sel = w.getSelectionRange()
        if len(sel) == 2:
            start,end = sel
        else:
            start,end = None,None
    else:
        start,end = None,None

    return (self.in_headline,p,w,insert,start,end)
#@-node:ekr.20031218072017.3090:save
#@+node:ekr.20031218072017.3091:showSuccess
def showSuccess(self,pos,newpos):

    """Displays the final result.

    Returns self.dummy_vnode, c.edit_widget(p) or c.frame.body.bodyCtrl with
    "insert" and "sel" points set properly."""

    c = self.c ; p = self.p
    sparseFind = c.config.getBool('collapse_nodes_during_finds')
    c.frame.bringToFront() # Needed on the Mac
    redraw = not p.isVisible(c)
    c.beginUpdate()
    try:
        if sparseFind and not c.currentPosition().isAncestorOf(p):
            # New in Leo 4.4.2: show only the 'sparse' tree when redrawing.
            for p2 in c.currentPosition().self_and_parents_iter():
                    p2.contract()
                    redraw = True
        for p in self.p.parents_iter():
            if not p.isExpanded():
                p.expand()
                redraw = True
        p = self.p
        if not p: g.trace('can not happen: self.p is None')
        c.selectPosition(p)
    finally:
        c.endUpdate(redraw)
    if self.in_headline:
        c.editPosition(p)
    # Set the focus and selection after the redraw.
    w = g.choose(self.in_headline,c.edit_widget(p),c.frame.body.bodyCtrl)
    c.widgetWantsFocusNow(w)
    # New in 4.4a3: a much better way to ensure progress in backward searches.
    insert = g.choose(self.reverse,min(pos,newpos),max(pos,newpos))
    #g.trace('reverse,pos,newpos,insert',self.reverse,pos,newpos,insert)
    w.setSelectionRange(pos,newpos,insert=insert)
    w.seeInsertPoint()
    if self.wrap and not self.wrapPosition:
        self.wrapPosition = self.p
#@nonl
#@-node:ekr.20031218072017.3091:showSuccess
#@+node:ekr.20031218072017.1460:update_ivars (leoFind)
# New in Leo 4.4.3: This is now gui-independent code.

def update_ivars (self):

    """Called just before doing a find to update ivars from the find panel."""

    self.p = self.c.currentPosition()
    self.v = self.p.v

    for key in self.intKeys:
        # g.trace(self.svarDict.get(key))
        val = self.svarDict[key].get()
        setattr(self, key, val) # No more _flag hack.

    # Set ivars from radio buttons. Convert these to 1 or 0.
    search_scope = self.svarDict["radio-search-scope"].get()
    self.suboutline_only = g.choose(search_scope == "suboutline-only",1,0)
    self.node_only       = g.choose(search_scope == "node-only",1,0)
    self.selection       = g.choose(search_scope == "selection-only",1,0)

    # New in 4.3: The caller is responsible for removing most trailing cruft.
    # Among other things, this allows Leo to search for a single trailing space.
    s = self.find_ctrl.getAllText()
    s = g.toUnicode(s,g.app.tkEncoding)
    # g.trace(repr(s))
    if s and s[-1] in ('\r','\n'):
        s = s[:-1]
    self.find_text = s

    s = self.change_ctrl.getAllText()
    if s and s[-1] in ('\r','\n'):
        s = s[:-1]
    s = g.toUnicode(s,g.app.tkEncoding)
    self.change_text = s
#@-node:ekr.20031218072017.1460:update_ivars (leoFind)
#@-node:ekr.20031218072017.3082:Initing & finalizing
#@-node:ekr.20061212084717:class leoFind
#@+node:ekr.20031218072017.3063:findNextCommand
# The user has selected the "Find Next" menu item.

def findNextCommand(self,c):

    self.setup_command()

    if self.script_search:
        self.doFindScript()
    else:
        self.findNext()
#@-node:ekr.20031218072017.3063:findNextCommand
#@+node:ekr.20031218072017.3066:setup_command
# Initializes a search when a command is invoked from the menu.

def setup_command(self):

    # g.trace('leoFind')

    if 0: # We _must_ retain the editing status for incremental searches!
        self.c.endEditing()

    self.update_ivars()
#@-node:ekr.20031218072017.3066:setup_command
#@+node:ekr.20031218072017.3074:findNext
def findNext(self,initFlag=True):

    c = self.c
    if not self.checkArgs():
        return

    if initFlag:
        self.initInHeadline()
        data = self.save()
        self.initInteractiveCommands()
    else:
        data = self.save()

    pos, newpos = self.findNextMatch()

    if pos is None:
        if self.wrapping:
            g.es("end of wrapped search")
        else:
            g.es("not found","'%s'" % (self.find_text))
        self.restore(data)
    else:
        self.showSuccess(pos,newpos)
#@-node:ekr.20031218072017.3074:findNext
#@+node:ekr.20031218072017.3075:findNextMatch
# Resumes the search where it left off.
# The caller must call set_first_incremental_search or set_first_batch_search.

def findNextMatch(self):

    c = self.c ; trace = self.trace

    if trace: g.trace('entry',g.callers())

    if not self.search_headline and not self.search_body:
        if trace: g.trace('nothing to search')
        return None, None

    if len(self.find_text) == 0:
        if trace: g.trace('no find text')
        return None, None

    p = self.p ; self.errors = 0
    attempts = 0
    self.backwardAttempts = 0

    # New in Leo 4.4.8: precompute the regexp for regexHelper.
    if self.pattern_match:
        try: # Precompile the regexp.
            flags = re.MULTILINE
            if self.ignore_case: flags |= re.IGNORECASE
            self.re_obj = re.compile(self.find_text,flags)
        except Exception:
            g.es('invalid regular expression:',pattern,color='blue')
            self.errors += 1 # Abort the search.
            return None,None

    while p:
        pos, newpos = self.search()
        if trace: g.trace('attempt','pos',pos,'p',p.headString())
        if pos is not None:
            if self.mark_finds:
                p.setMarked()
                c.frame.tree.drawIcon(p) # redraw only the icon.
            if trace: g.trace('success',pos,newpos)
            return pos, newpos
        elif self.errors:
            g.trace('find errors')
            return None,None # Abort the search.
        elif self.node_only:
            if trace: g.trace('fail: node only')
            return None,None # We are only searching one node.
        else:
            if trace: g.trace('failed attempt',p)
            attempts += 1
            p = self.p = self.selectNextPosition()

    if trace: g.trace('attempts',attempts,'backwardAttempts',self.backwardAttempts)
    return None, None
#@-node:ekr.20031218072017.3075:findNextMatch
#@+node:ekr.20031218072017.3077:search & helpers
def search (self):

    """Search s_ctrl for self.find_text under the control of the
    whole_word, ignore_case, and pattern_match ivars.

    Returns (pos, newpos) or (None,None)."""

    c = self.c ; p = self.p ; w = self.s_ctrl ; trace = self.trace
    index = w.getInsertPoint()
    s = w.getAllText()

    # g.trace(index,repr(s[index:index+20]))
    stopindex = g.choose(self.reverse,0,len(s)) # 'end' doesn't work here.
    pos,newpos = self.searchHelper(s,index,stopindex,self.find_text,
        backwards=self.reverse,nocase=self.ignore_case,
        regexp=self.pattern_match,word=self.whole_word)

    # g.trace('pos,newpos',pos,newpos)
    if pos == -1:
        if trace: g.trace('** pos is -1',pos,newpos)
        return None,None
    << fail if we are passed the wrap point >>
    insert = g.choose(self.reverse,min(pos,newpos),max(pos,newpos))
    w.setSelectionRange(pos,newpos,insert=insert)

    if trace: g.trace('** returns',pos,newpos)
    return pos,newpos
#@+node:ekr.20060526140328:<< fail if we are passed the wrap point >>
if self.wrapping and self.wrapPos is not None and self.wrapPosition and p == self.wrapPosition:

    if self.reverse and pos < self.wrapPos:
        if trace: g.trace("** reverse wrap done",pos,newpos)
        return None, None

    if not self.reverse and newpos > self.wrapPos:
        if trace: g.trace('** wrap done',pos,newpos)
        return None, None
#@-node:ekr.20060526140328:<< fail if we are passed the wrap point >>
#@+node:ekr.20060526081931:searchHelper & allies
def searchHelper (self,s,i,j,pattern,backwards,nocase,regexp,word,swapij=True):

    trace = self.trace

    if swapij and backwards: i,j = j,i

    if trace: g.trace('back,nocase,regexp,word,',
        backwards,nocase,regexp,word,i,j,repr(s[i:i+20]))

    if not s[i:j] or not pattern:
        if trace: g.trace('empty',i,j,'len(s)',len(s),'pattern',pattern)
        return -1,-1

    if regexp:
        pos,newpos = self.regexHelper(s,i,j,pattern,backwards,nocase)
    elif backwards:
        pos,newpos = self.backwardsHelper(s,i,j,pattern,nocase,word)
    else:
        pos,newpos = self.plainHelper(s,i,j,pattern,nocase,word)

    if trace: g.trace('returns',pos,newpos)
    return pos,newpos
#@+node:ekr.20060526092203:regexHelper
def regexHelper (self,s,i,j,pattern,backwards,nocase):

    re_obj = self.re_obj # Use the pre-compiled object
    if not re_obj:
        g.trace('can not happen: no re_obj')
        return -1,-1

    # try:
        # flags = re.MULTILINE
        # if nocase: flags |= re.IGNORECASE
        # re_obj = re.compile(pattern,flags)
    # except Exception:
        # g.es('invalid regular expression:',pattern,color='blue')
        # self.errors += 1 # Abort the search.
        # return -1, -1

    if backwards: # Scan to the last match.  We must use search here.
        last_mo = None ; i = 0
        while i < len(s):
            mo = re_obj.search(s,i,j)
            if not mo: break
            i += 1 ; last_mo = mo
        mo = last_mo
    else:
        mo = re_obj.search(s,i,j)

    if 0:
        g.trace('i',i,'j',j,'s[i:j]',repr(s[i:j]),
            'mo.start',mo and mo.start(),'mo.end',mo and mo.end())

    while mo and 0 <= i < len(s):
        if mo.start() == mo.end():
            if backwards:
                # Search backward using match instead of search.
                i -= 1
                while 0 <= i < len(s):
                    mo = re_obj.match(s,i,j)
                    if mo: break
                    i -= 1
            else:
                i += 1 ; mo = re_obj.search(s,i,j)
        else:
            self.match_obj = mo
            return mo.start(),mo.end()
    self.match_obj = None
    return -1,-1
#@-node:ekr.20060526092203:regexHelper
#@+node:ekr.20060526140744:backwardsHelper
debugIndices = []

@
rfind(sub [,start [,end]])

Return the highest index in the string where substring sub is found, such that
sub is contained within s[start,end]. Optional arguments start and end are
interpreted as in slice notation. Return -1 on failure.
@c

def backwardsHelper (self,s,i,j,pattern,nocase,word):

    debug = False
    if nocase:
        s = s.lower() ; pattern = pattern.lower() # Bug fix: 10/5/06: At last the bug is found!
    pattern = self.replaceBackSlashes(pattern)
    n = len(pattern)

    if i < 0 or i > len(s) or j < 0 or j > len(s):
        g.trace('bad index: i = %s, j = %s' % (i,j))
        i = 0 ; j = len(s)

    if debug and (s and i == 0 and j == 0):
        g.trace('two zero indices')

    self.backwardAttempts += 1

    # short circuit the search: helps debugging.
    if s.find(pattern) == -1:
        if debug:
            self.debugCount += 1
            if self.debugCount < 50:
                g.trace(i,j,'len(s)',len(s),self.p.headString())
        return -1,-1

    if word:
        while 1:
            k = s.rfind(pattern,i,j)
            if debug: g.trace('**word** %3s %3s %5s -> %s %s' % (i,j,g.choose(j==len(s),'(end)',''),k,self.p.headString()))
            if k == -1: return -1, -1
            if self.matchWord(s,k,pattern):
                return k,k+n
            else:
                j = max(0,k-1)
    else:
        k = s.rfind(pattern,i,j)
        if debug: g.trace('%3s %3s %5s -> %s %s' % (i,j,g.choose(j==len(s),'(end)',''),k,self.p.headString()))
        if k == -1:
            return -1, -1
        else:
            return k,k+n
#@-node:ekr.20060526140744:backwardsHelper
#@+node:ekr.20060526093531:plainHelper
def plainHelper (self,s,i,j,pattern,nocase,word):

    trace = self.trace

    # if trace: g.trace(i,j,repr(s[i:i+20]),'pattern',repr(pattern),'word',repr(word))
    if trace: g.trace(i,j,repr(s[i:i+20]))

    if nocase:
        s = s.lower() ; pattern = pattern.lower()
    pattern = self.replaceBackSlashes(pattern)
    n = len(pattern)
    if word:
        while 1:
            k = s.find(pattern,i,j)
            # g.trace(k,n)
            if k == -1:
                if trace: g.trace('no match word',i)
                return -1, -1
            elif self.matchWord(s,k,pattern):
                if trace: g.trace('match word',k)
                return k, k + n
            else: i = k + n
    else:
        k = s.find(pattern,i,j)
        if k == -1:
            if trace: g.trace('no match word',i)
            return -1, -1
        else:
            if trace: g.trace('match', k)
            return k, k + n
#@-node:ekr.20060526093531:plainHelper
#@+node:ekr.20060526140744.1:matchWord
def matchWord(self,s,i,pattern):

    trace = self.trace

    pattern = self.replaceBackSlashes(pattern)
    if not s or not pattern or not g.match(s,i,pattern):
        if trace: g.trace('empty')
        return False

    pat1,pat2 = pattern[0],pattern[-1]
    # n = self.patternLen(pattern)
    n = len(pattern)
    ch1 = 0 <= i-1 < len(s) and s[i-1] or '.'
    ch2 = 0 <= i+n < len(s) and s[i+n] or '.'

    isWordPat1 = g.isWordChar(pat1)
    isWordPat2 = g.isWordChar(pat2)
    isWordCh1 = g.isWordChar(ch1)
    isWordCh2 = g.isWordChar(ch2)

    # g.trace('i',i,'ch1,ch2,pat',repr(ch1),repr(ch2),repr(pattern))

    inWord = isWordPat1 and isWordCh1 or isWordPat2 and isWordCh2
    if trace: g.trace('returns',not inWord)
    return not inWord

#@-node:ekr.20060526140744.1:matchWord
#@+node:ekr.20070105165924:replaceBackSlashes
def replaceBackSlashes (self,s):

    '''Carefully replace backslashes in a search pattern.'''

    # This is NOT the same as s.replace('\\n','\n').replace('\\t','\t').replace('\\\\','\\')
    # because there is no rescanning.

    i = 0
    while i + 1 < len(s):
        if s[i] == '\\':
            ch = s[i+1]
            if ch == '\\':
                s = s[:i] + s[i+1:] # replace \\ by \
            elif ch == 'n':
                s = s[:i] + '\n' + s[i+2:] # replace the \n by a newline
            elif ch == 't':
                s = s[:i] + '\t' + s[i+2:] # replace \t by a tab
            else:
                i += 1 # Skip the escaped character.
        i += 1

    if self.trace: g.trace(repr(s))
    return s
#@-node:ekr.20070105165924:replaceBackSlashes
#@-node:ekr.20060526081931:searchHelper & allies
#@-node:ekr.20031218072017.3077:search & helpers
#@+node:ekr.20060205105950.1:generalChangeHelper
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.currentPosition()
    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
#@-node:ekr.20071213095537:Fixed bug: replace didn't always start in the correct place
#@+node:ekr.20071214094952:fixed colorizer bug
@

The last char was not colored.

<tag />
#@nonl
#@-node:ekr.20071214094952:fixed colorizer bug
#@+node:ekr.20071214150627:Fixed clone focus bug
@nocolor
http://sourceforge.net/forum/message.php?msg_id=4676037

The fix was to insert a missing begin/endUpdate.

@color
#@nonl
#@+node:ekr.20031218072017.2916:goToNextClone
def goToNextClone (self,event=None):

    '''Select the next node that is a clone of the selected node.'''

    c = self ; cc = c.chapterController ; p = c.currentPosition()
    if not p: return
    if not p.isCloned():
        g.es('not a clone:',p.headString(),color='blue')
        return

    t = p.v.t
    p.moveToThreadNext()
    wrapped = False
    while 1:
        if p and p.v.t == t:
            break
        elif p:
            p.moveToThreadNext()
        elif wrapped:
            break
        else:
            wrapped = True
            p = c.rootPosition()

    if not p: g.es("done",color="blue")

    c.beginUpdate()
    try:
        if cc:
            name = cc.findChapterNameForPosition(p)
            cc.selectChapterByName(name)
            c.frame.tree.expandAllAncestors(p)

        c.selectPosition(p)
    finally:
        c.endUpdate() 
#@-node:ekr.20031218072017.2916:goToNextClone
#@-node:ekr.20071214150627:Fixed clone focus bug
#@+node:ekr.20071215160319:Removed call to printLine in change-all logic
#@-node:ekr.20071215160319:Removed call to printLine in change-all logic
#@+node:ekr.20071217082302:Fixed crash when copying or cloning to non-exisitent chapter
<type 'exceptions.AttributeError'> Exception in Tk callback
  Function: <function masterBindKeyCallback at 0x019EC5B0> (type: <type 'function'>)
  Args: (<Tkinter.Event instance at 0x030A4530>,)
  Event type: KeyPress (type num: 2)
Traceback (innermost last):
  File "c:\python25\lib\site-packages\Pmw\Pmw_1_3\lib\PmwBase.py", line 1747, in __call__
    return apply(self.func, args)
  File "C:\prog\tigris-cvs\leo\src\leoKeys.py", line 2155, in masterBindKeyCallback
    return k.masterKeyHandler(event,stroke=stroke)
  File "C:\prog\tigris-cvs\leo\src\leoKeys.py", line 3026, in masterKeyHandler
    return k.getArg(event,stroke=stroke)
  File "C:\prog\tigris-cvs\leo\src\leoKeys.py", line 2843, in getArg
    if handler: handler(event)
  File "C:\prog\tigris-cvs\leo\src\leoChapters.py", line 183, in copyNodeToChapter
    cc.copyNodeToChapterHelper(k.arg)
  File "C:\prog\tigris-cvs\leo\src\leoChapters.py", line 200, in copyNodeToChapterHelper
    parent = cc.getChapterNode(toChapter.name)
<type 'exceptions.AttributeError'>: 'NoneType' object has no attribute 'name'
#@nonl
#@+node:ekr.20070317085437.50:cc.cloneNodeToChapter & helper
def cloneNodeToChapter (self,event=None):

    '''Prompt for a chapter name,
    then clone the selected node to the chapter.'''

    cc = self ; k = cc.c.k ; tag = 'clone-node-to-chapter'
    state = k.getState(tag)

    if state == 0:
        names = cc.chaptersDict.keys()
        prefix = 'Clone node to chapter: '
        k.setLabelBlue(prefix,protect=True)
        k.getArg(event,tag,1,self.cloneNodeToChapter,prefix=prefix,tabList=names)
    else:
        k.clearState()
        k.resetLabel()
        if k.arg:
            cc.cloneNodeToChapterHelper(k.arg)
#@nonl
#@+node:ekr.20070604155815.1:cc.cloneToChapterHelper
def cloneNodeToChapterHelper (self,toChapterName):

    cc = self ; c = cc.c ;  u = c.undoer ; undoType = 'Clone Node To Chapter'
    p = c.currentPosition() ; h = p.headString()
    fromChapter = cc.getSelectedChapter()
    toChapter = cc.getChapter(toChapterName)
    if not toChapter:
        return cc.error('no such chapter: %s' % toChapterName)
    if fromChapter.name == 'main' and h.startswith('@chapter'):
        return cc.error('can not clone @chapter node')
    # g.trace('from',fromChapter.name,'to',toChapter)

    c.beginUpdate()
    try:
        # Open the group undo.
        c.undoer.beforeChangeGroup(p,undoType)
        # Do the clone.  c.clone handles the inner undo.
        clone = c.clone()
        # Do the move.
        undoData2 = u.beforeMoveNode(clone)
        clone.unlink()
        if toChapter.name == 'main':
            clone.moveAfter(toChapter.p)
        else:
            parent = cc.getChapterNode(toChapter.name)
            clone.moveToLastChildOf(parent)
        u.afterMoveNode(clone,'Move Node',undoData2,dirtyVnodeList=[])
        c.selectPosition(clone)
        c.setChanged(True)
        # Close the group undo.
        # Only the ancestors of the moved node get set dirty.
        dirtyVnodeList = clone.setAllAncestorAtFileNodesDirty()
        c.undoer.afterChangeGroup(clone,undoType,reportFlag=False,dirtyVnodeList=dirtyVnodeList)
    finally:
        c.endUpdate(False)

    toChapter.p = clone.copy()
    toChapter.select()
    fromChapter.p = p.copy()
#@-node:ekr.20070604155815.1:cc.cloneToChapterHelper
#@-node:ekr.20070317085437.50:cc.cloneNodeToChapter & helper
#@+node:ekr.20070317085437.51:cc.copyNodeToChapter & helper
def copyNodeToChapter (self,event=None):

    '''Prompt for a chapter name,
    then copy the selected node to the chapter.'''

    cc = self ; k = cc.c.k ; tag = 'copy-node-to-chapter'
    state = k.getState(tag)

    if state == 0:
        names = cc.chaptersDict.keys()
        prefix = 'Copy node to chapter: '
        k.setLabelBlue(prefix,protect=True)
        k.getArg(event,tag,1,self.copyNodeToChapter,prefix=prefix,tabList=names)
    else:
        k.clearState()
        k.resetLabel()
        if k.arg:
            cc.copyNodeToChapterHelper(k.arg)
#@nonl
#@+node:ekr.20070604155815.2:cc.copyNodeToChapterHelper
def copyNodeToChapterHelper (self,toChapterName):

    cc = self ; c = cc.c ; u = c.undoer ; undoType = 'Copy Node To Chapter'
    p = c.currentPosition() ; h = p.headString()
    fromChapter = cc.getSelectedChapter()
    toChapter = cc.getChapter(toChapterName)
    if not toChapter:
        return cc.error('no such chapter: %s' % toChapterName)
    if fromChapter.name == 'main' and h.startswith('@chapter'):
        return cc.error('can not copy @chapter node')
    # g.trace('from',fromChapter.name,'to',toChapter.name)

    c.beginUpdate()
    try:
        # For undo, we treat the copy like a pasted (inserted) node.
        # Use parent as the node to select for undo.
        parent = cc.getChapterNode(toChapter.name)
        undoData = u.beforeInsertNode(parent,pasteAsClone=False,copiedBunchList=[])
        s = c.fileCommands.putLeoOutline()
        p2 = c.fileCommands.getLeoOutline(s)
        p2.unlink()
        p2.moveToLastChildOf(parent)
        c.selectPosition(p2)
        u.afterInsertNode(p2,undoType,undoData)
        c.setChanged(True)
    finally:
        c.endUpdate(False)

    toChapter.p = p2.copy()
    toChapter.select()
    fromChapter.p = p.copy()
#@-node:ekr.20070604155815.2:cc.copyNodeToChapterHelper
#@-node:ekr.20070317085437.51:cc.copyNodeToChapter & helper
#@-node:ekr.20071217082302:Fixed crash when copying or cloning to non-exisitent chapter
#@+node:ekr.20071217090919:Repaired damaged minusnode.gif icon
#@-node:ekr.20071217090919:Repaired damaged minusnode.gif icon
#@-node:ekr.20071211113202.1:Bugs fixed
#@+node:ekr.20071211113202.2:New features
#@+node:ekr.20071210054816:Applied Terry Brown's config patch
@nocolor
The patch attached against leoConfig.py (current CVS) adds a machine
specific config file to the config files Leo loads.

I synchronize files between multiple (3) machines. This means I can't
use different myLeoSettings files on each.

Tk fonts are screen dpi sensitive.  But (in Linux at least)
Tk's attempts to switch a font you picked to something equivalent at a
different dpi always result in a font that looks terrible.

So every time I switch machines I edit ~/myLeoSettings.leo to move the
font defs I picked for the current machine to the bottom of the body
text for the appropriate settings node, and then restart Leo.

This patch means I don't have to do that any more.  I'm sure there are
other uses for it.  Python doesn't seem to have a generic machine name
finding function, hence the sequence of alternatives.

Cheers -Terry


-----Inline Attachment Follows-----

Index: leo/src/leoConfig.py
===================================================================
RCS file: /cvs/leo/src/leoConfig.py,v
retrieving revision 1.54
diff -r1.54 leoConfig.py
1159a1160,1177
>        machineSettingsFile = None
>        #@    << determine machine settings file name >>
>        #@+node:tbrown.20071208101033:<< determine machine settings file name >>
>        try:
>            import os
>            machineSettingsFile = os.getenv('HOSTNAME')
>            if not machineSettingsFile:
>                machineSettingsFile = os.getenv('COMPUTERNAME')
>            if not machineSettingsFile:
>                import socket
>                machineSettingsFile = socket.gethostname()
>        except:
>            machineSettingsFile = None
>        if machineSettingsFile:
>            machineSettingsFile+='LeoSettings.leo'
>        #@nonl
>        #@-node:tbrown.20071208101033:<< determine machine settings file name >>
>        #@nl
1165a1184
>            ('machineConfigFile',    g.app.homeDir,          machineSettingsFile),
1168a1188,1190
>
>            if not fileName: continue
>
1638a1661
>            (self.machineConfigFile,False),

@color
#@nonl
#@+node:ekr.20041120064303:g.app.config.readSettingsFiles & helpers
def readSettingsFiles (self,fileName,verbose=True):

    seen = []
    self.write_recent_files_as_needed = False # Will be set later.
    << define localDirectory, localConfigFile & myLocalConfigFile >>

    # Init settings from leoSettings.leo and myLeoSettings.leo files.
    for path,localFlag in (
        (self.globalConfigFile,False),
        (self.homeFile,False),
        (localConfigFile,False),
        (self.myGlobalConfigFile,False),
        (self.myHomeConfigFile,False),
        (self.machineConfigFile,False),
        (myLocalConfigFile,False),
        (fileName,True),
    ):
        if path and path.lower() not in seen:
            seen.append(path.lower())
            if verbose and not g.app.unitTesting and not self.silent and not g.app.batchMode:
                s = 'reading settings in %s' % path
                # This occurs early in startup, so use the following instead of g.es_print.
                s = g.toEncodedString(s,'ascii')
                print s
                g.app.logWaiting.append((s+'\n','blue'),)

            c = self.openSettingsFile(path)
            if c:
                self.updateSettings(c,localFlag)
                g.app.destroyWindow(c.frame)
                self.write_recent_files_as_needed = c.config.getBool('write_recent_files_as_needed')
                self.setIvarsFromSettings(c)
    self.readRecentFiles(localConfigFile)
    self.inited = True
    self.setIvarsFromSettings(None)
#@+node:ekr.20061028082834:<< define localDirectory, localConfigFile & myLocalConfigFile >>
# This can't be done in initSettingsFiles because the local directory does not exits.
localDirectory = g.os_path_dirname(fileName)

#  Set the local leoSettings.leo file.
localConfigFile = g.os_path_join(localDirectory,'leoSettings.leo')
if not g.os_path_exists(localConfigFile):
    localConfigFile = None

# Set the local myLeoSetting.leo file.
myLocalConfigFile = g.os_path_join(localDirectory,'myLeoSettings.leo')
if not g.os_path_exists(myLocalConfigFile):
    myLocalConfigFile = None
#@nonl
#@-node:ekr.20061028082834:<< define localDirectory, localConfigFile & myLocalConfigFile >>
#@+node:ekr.20041117085625:g.app.config.openSettingsFile
def openSettingsFile (self,path):

    theFile,isZipped = g.openLeoOrZipFile(path)
    if not theFile: return None

    # Similar to g.openWithFileName except it uses a null gui.
    # Changing g.app.gui here is a major hack.
    oldGui = g.app.gui
    g.app.gui = leoGui.nullGui("nullGui")
    c,frame = g.app.newLeoCommanderAndFrame(
        fileName=path,relativeFileName=None,
        initEditCommanders=False,updateRecentFiles=False)
    frame.log.enable(False)
    c.setLog()
    g.app.lockLog()
    ok = frame.c.fileCommands.open(
        theFile,path,readAtFileNodesFlag=False,silent=True) # closes theFile.
    g.app.unlockLog()
    frame.openDirectory = g.os_path_dirname(path)
    g.app.gui = oldGui
    return ok and c
#@-node:ekr.20041117085625:g.app.config.openSettingsFile
#@+node:ekr.20051013161232:g.app.config.updateSettings
def updateSettings (self,c,localFlag):

    d = self.readSettings(c)

    if d:
        d['_hash'] = theHash = c.hash()
        if localFlag:
            self.localOptionsDict[theHash] = d
        else:
            self.localOptionsList.insert(0,d)

    if 0: # Good trace.
        if localFlag:
            g.trace(c.fileName())
            g.trace(d and d.keys())
#@-node:ekr.20051013161232:g.app.config.updateSettings
#@-node:ekr.20041120064303:g.app.config.readSettingsFiles & helpers
#@+node:ekr.20041117083857:initSettingsFiles
def initSettingsFiles (self):

    """Set self.globalConfigFile, self.homeFile, self.machineConfigFile and self.myConfigFile."""

    settingsFile = 'leoSettings.leo'
    mySettingsFile = 'myLeoSettings.leo'
    machineConfigFile = self.getMachineName()

    for ivar,theDir,fileName in (
        ('globalConfigFile',    g.app.globalConfigDir,  settingsFile),
        ('homeFile',            g.app.homeDir,          settingsFile),
        ('myGlobalConfigFile',  g.app.globalConfigDir,  mySettingsFile),
        ('myHomeConfigFile',    g.app.homeDir,          mySettingsFile),
        ('machineConfigFile',   g.app.homeDir,          machineConfigFile),
    ):
        # The same file may be assigned to multiple ivars:
        # readSettingsFiles checks for such duplications.
        path = g.os_path_join(theDir,fileName)
        if g.os_path_exists(path):
            setattr(self,ivar,path)
        else:
            setattr(self,ivar,None)
    if 0:
        g.trace('global file:',self.globalConfigFile)
        g.trace('home file:',self.homeFile)
        g.trace('myGlobal file:',self.myGlobalConfigFile)
        g.trace('myHome file:',self.myHomeConfigFile)
#@nonl
#@+node:ekr.20071211112804:getMachineName
def getMachineName (self):

    try:
        import os
        name = os.getenv('HOSTNAME')
        if not name:
            name = os.getenv('COMPUTERNAME')
        if not name:
            import socket
            name = socket.gethostname()
    except Exception:
        name = ''

    if name:
        name +='LeoSettings.leo'

    # g.trace(name)

    return name
#@-node:ekr.20071211112804:getMachineName
#@-node:ekr.20041117083857:initSettingsFiles
#@-node:ekr.20071210054816:Applied Terry Brown's config patch
#@+node:ekr.20071210090623:Applied plumloco's patch to plugins_menu.py
@nocolor

The version of plugins_menu.py at http://leo.zwiki.org/Plumloco separates out
the gui dependent dialogs (the menus are already gui indepandant) and made them
general purpose dialogs available to all plugins.

I have also slightly enhanced the dialogs to provide user defined buttons. 
#@nonl
#@-node:ekr.20071210090623:Applied plumloco's patch to plugins_menu.py
#@+node:ekr.20071129095408:Upgraded to Pmw 1.3
#@-node:ekr.20071129095408:Upgraded to Pmw 1.3
#@+node:ekr.20071211132156:Added find-next-clone command
flag = False

if p.isCloned():
    p.moveToThreadNext()

while p:
    if p.isCloned():
        flag = True ; break
    else:
        p.moveToThreadNext()

if flag:
    c.beginUpdate()
    try:
        c.frame.tree.expandAllAncestors(p)
        c.selectPosition(p)
    finally:
        c.endUpdate()

else:
    g.es('No more clones',color='blue')
#@+node:ekr.20071213123942:findNextClone
def findNextClone (self,event=None):

    '''Select the next cloned node.'''

    c = self ; p = c.currentPosition() ; flag = False
    if not p: return

    if p.isCloned():
        p.moveToThreadNext()

    while p:
        if p.isCloned():
            flag = True ; break
        else:
            p.moveToThreadNext()

    if flag:
        c.beginUpdate()
        try:
            cc = c.chapterController
            if cc:
                name = cc.findChapterNameForPosition(p)
                cc.selectChapterByName(name)
            c.frame.tree.expandAllAncestors(p)
            c.selectPosition(p)
        finally:
            c.endUpdate()
    else:
        g.es('no more clones',color='blue')
#@-node:ekr.20071213123942:findNextClone
#@-node:ekr.20071211132156:Added find-next-clone command
#@+node:ekr.20071115055315:Added toggle-sparse-move command
#@+node:ekr.20071213185710:c.toggleSparseMove
def toggleSparseMove (self,event=None):

    c = self ; p = c.currentPosition()
    tag = 'sparse_move_outline_left'

    sparseMove = not c.config.getBool(tag)
    c.config.set(p, tag, sparseMove)
    g.es(tag,'=',sparseMove,color='blue')
#@-node:ekr.20071213185710:c.toggleSparseMove
#@+node:ekr.20031218072017.1770:moveOutlineLeft
def moveOutlineLeft (self,event=None):

    '''Move the selected node left if possible.'''

    c = self ; u = c.undoer ; p = c.currentPosition()
    if not p: return
    if not c.canMoveOutlineLeft():
        if c.hoistStack: self.cantMoveMessage()
        c.treeFocusHelper()
        return
    if not p.hasParent():
        c.treeFocusHelper()
        return

    inAtIgnoreRange = p.inAtIgnoreRange()
    parent = p.parent()
    sparseMove = c.config.getBool('sparse_move_outline_left')
    c.beginUpdate()
    try: # In update...
        c.endEditing()
        undoData = u.beforeMoveNode(p)
        dirtyVnodeList = p.setAllAncestorAtFileNodesDirty()
        p.moveAfter(parent)
        if inAtIgnoreRange and not p.inAtIgnoreRange():
            # The moved nodes have just become newly unignored.
            p.setDirty() # Mark descendent @thin nodes dirty.
        else: # No need to mark descendents dirty.
            dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty()
            dirtyVnodeList.extend(dirtyVnodeList2)
        c.setChanged(True)
        u.afterMoveNode(p,'Move Left',undoData,dirtyVnodeList)
        if sparseMove: # New in Leo 4.4.2
            parent.contract()
    finally:
        c.selectPosition(p) # Also sets rootPosition.
        c.endUpdate()
        # c.treeWantsFocusNow()
        c.treeFocusHelper()
    c.updateSyntaxColorer(p) # Moving can change syntax coloring.
#@nonl
#@-node:ekr.20031218072017.1770:moveOutlineLeft
#@-node:ekr.20071115055315:Added toggle-sparse-move command
#@+node:ekr.20071214141513:Added support for @data nodes in @settings trees
#@+node:ekr.20071214140900:doData
def doData (self,p,kind,name,val):

    s = p.bodyString()
    lines = g.splitLines(s)
    data = [z.strip() for z in lines if z.strip() and not z.startswith('#')]

    self.set(p,kind,name,data)
#@-node:ekr.20071214140900:doData
#@-node:ekr.20071214141513:Added support for @data nodes in @settings trees
#@+node:ekr.20071214061555:Added @auto xml script
# Use @data import_xml_tags setting to specify the xml tags that act as organizers.
#@nonl
#@+node:ekr.20071214072145.1:class xmlScanner
class xmlScanner (baseScannerClass):

    @others
#@+node:ekr.20071214072451: __init__ (xmlScanner)
def __init__ (self,importCommands,atAuto):

    # Init the base class.
    baseScannerClass.__init__(self,importCommands,atAuto=atAuto,language='xml')
        # sets self.c

    # Set the parser delims.
    self.blockCommentDelim1 = '<!--'
    self.blockCommentDelim2 = '-->'
    self.blockDelim1 = None 
    self.blockDelim2 = None
    self.classTags = [] # Inited by import_xml_tags setting.
    self.extraIdChars = None
    self.functionTags = []
    self.lineCommentDelim = None
    self.lineCommentDelim2 = None
    self.outerBlockDelim1 = None
    self.outerBlockDelim2 = None
    self.outerBlockEndsDecls = False
    self.sigHeadExtraTokens = []
    self.sigFailTokens = []

    # Overrides more attributes.
    self.hasClasses = True
    self.hasFunctions = False
    self.strict = False
    self.trace = False

    self.addTags()

#@-node:ekr.20071214072451: __init__ (xmlScanner)
#@+node:ekr.20071214131818:addTags
def addTags (self):

    '''Add items to self.class/functionTags and from settings.'''

    c = self.c

    for ivar,setting in (
        ('classTags','import_xml_tags',),
        # ('functionTags','import_xml_function_tags'),
    ):
        aList = getattr(self,ivar)
        aList2 = c.config.getData(setting) or []
        aList.extend(aList2)
        # g.trace(ivar,aList)
#@-node:ekr.20071214131818:addTags
#@+node:ekr.20071214072924.4:startsHelper & helpers
def startsHelper(self,s,i,kind,tags):
    '''return True if s[i:] starts a class or function.
    Sets sigStart, sigEnd, sigId and codeEnd ivars.'''

    trace = self.trace ; verbose = False
    self.codeEnd = self.sigEnd = self.sigId = None

    # Underindented lines can happen in any language, not just Python.
    # The skipBlock method of the base class checks for such lines.
    self.startSigIndent = self.getLeadingIndent(s,i)

    # Get the tag that starts the class or function.
    if not g.match(s,i,'<'): return False
    self.sigStart = i
    i += 1
    j = g.skip_ws_and_nl(s,i)
    i = self.skipId(s,j)
    self.sigId = theId = s[j:i] # Set sigId ivar 'early' for error messages.
    if not theId: return False

    if theId not in tags:
        if trace and verbose: g.trace('**** %s theId: %s not in tags: %s' % (kind,theId,tags))
        return False

    if trace and verbose: g.trace(theId)
    classId = '' 
    sigId = theId

    # Complete the opening tag.
    i, ok = self.skipToEndOfTag(s,i)
    if not ok:
        if trace and verbose: g.trace('no tail',g.get_line(s,i))
        return False
    sigEnd = i

    # A trick: make sure the signature ends in a newline,
    # even if it overlaps the start of the block.
    if 0:
        if not g.match(s,sigEnd,'\n') and not g.match(s,sigEnd-1,'\n'):
            if trace and verbose: g.trace('extending sigEnd')
            sigEnd = g.skip_line(s,sigEnd)

    i,ok = self.skipToMatchingTag(s,i,theId)
    if not ok:
        if trace: g.trace('no matching tag',theId)
        return False

    # Success: set the ivars.
    self.sigStart = self.adjustDefStart(s,self.sigStart)
    self.codeEnd = i
    self.sigEnd = sigEnd
    self.sigId = sigId
    self.classId = classId

    # Note: backing up here is safe because
    # we won't back up past scan's 'start' point.
    # Thus, characters will never be output twice.
    k = self.sigStart
    if not g.match(s,k,'\n'):
        self.sigStart = g.find_line_start(s,k)

    # Issue this warning only if we have a real class or function.
    if 0: ### wrong.if trace: g.trace(kind,'returns\n'+s[self.sigStart:i])
        if s[self.sigStart:k].strip():
            self.error('%s definition does not start a line\n%s' % (
                kind,g.get_line(s,k)))

    if trace: g.trace(kind,'returns\n'+s[self.sigStart:i])
    return True
#@+node:ekr.20071214072924.3:skipToEndOfTag
def skipToEndOfTag(self,s,i):

    '''Skip to the end of an open tag.'''

    while i < len(s):
        progress = i
        if i == '"':
            i = self.skipString(s,i)
        elif g.match(s,i,'/>'):
            return i,False # Starts a self-contained tag.
        elif g.match(s,i,'>'):
            i += 1
            if g.match(s,i,'\n'): i += 1
            return i,True
        else:
            i += 1
        assert progress < i

    return i,False
#@-node:ekr.20071214072924.3:skipToEndOfTag
#@+node:ekr.20071214075117:skipToMatchingTag
def skipToMatchingTag (self,s,i,tag):

    while i < len(s):
        progress = i
        if i == '"':
            i = self.skipString(s,i)
        elif g.match(s,i,'</'):
            i += 2 ; j = i
            i = self.skipId(s,j)
            tag2 = s[j:i]
            if tag2.lower() == tag.lower():
                i,ok = self.skipToEndOfTag(s,i)
                return i,ok
        else:
            i += 1
        assert progress < i

    return i,False
#@-node:ekr.20071214075117:skipToMatchingTag
#@-node:ekr.20071214072924.4:startsHelper & helpers
#@-node:ekr.20071214072145.1:class xmlScanner
#@-node:ekr.20071214061555:Added @auto xml script
#@+node:ekr.20071215114822.1:Added text bindings to status area
# This fixes a focus problem.
#@nonl
#@+node:ekr.20071215114822:setBindings (tkStatusLine)
def setBindings (self):

    k = self.c.keyHandler ; w = self.textWidget

    w.bind('<Key>',k.masterKeyHandler)

    k.completeAllBindingsForWidget(w)
#@-node:ekr.20071215114822:setBindings (tkStatusLine)
#@-node:ekr.20071215114822.1:Added text bindings to status area
#@+node:ekr.20071027121956:Completed javascript import scanner
# regexps that look like section references cause problems, but that can not be helped.
#@nonl
#@+node:ekr.20070707073859:skipBlock
def skipBlock(self,s,i,delim1=None,delim2=None):

    '''Skip from the opening delim to *past* the matching closing delim.

    If no matching is found i is set to len(s)'''

    trace = False
    start = i
    if delim1 is None: delim1 = self.blockDelim1
    if delim2 is None: delim2 = self.blockDelim2
    match1 = g.choose(len(delim1)==1,g.match,g.match_word)
    match2 = g.choose(len(delim2)==1,g.match,g.match_word)
    assert match1(s,i,delim1)
    level = 0 ; start = i
    startIndent = self.startSigIndent
    if trace: g.trace('***','startIndent',startIndent,g.callers())
    while i < len(s):
        progress = i
        if g.is_nl(s,i):
            backslashNewline = i > 0 and g.match(s,i-1,'\\\n')
            i = g.skip_nl(s,i)
            if not backslashNewline and not g.is_nl(s,i):
                j, indent = g.skip_leading_ws_with_indent(s,i,self.tab_width)
                line = g.get_line(s,j)
                if trace: g.trace('indent',indent,line)
                if indent < startIndent and line.strip():
                    # An non-empty underindented line.
                    # Issue an error unless it contains just the closing bracket.
                    if level == 1 and match2(s,j,delim2):
                        pass
                    else:
                        if j not in self.errorLines: # No error yet given.
                            self.errorLines.append(j)
                            self.underindentedLine(line)
        elif s[i] in (' ','\t',):
            i += 1 # speed up the scan.
        elif self.startsComment(s,i):
            i = self.skipComment(s,i)
        elif self.startsString(s,i):
            i = self.skipString(s,i)
        elif match1(s,i,delim1):
            level += 1 ; i += len(delim1)
        elif match2(s,i,delim2):
            level -= 1 ; i += len(delim2)
            # Skip junk following Pascal 'end'
            for z in self.blockDelim2Cruft:
                i2 = g.skip_ws(s,i)
                if g.match(s,i2,z):
                    i = i2 + len(z)
                    break
            if level <= 0:
                if trace: g.trace('returns\n',repr(s[start:i]))
                return i

        else: i += 1
        assert progress < i

    self.error('no block')
    if 1:
        print '** no block **'
        i,j = g.getLine(s,start)
        g.trace(i,s[i:j])
    else:
        if trace: g.trace('** no block')
    return start
#@-node:ekr.20070707073859:skipBlock
#@+node:ekr.20071211092214:Javascript spec
@nocolor

As far as I am aware, this is the definitive specification for javascript. 

http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf  

The most relavent part is below. This has caused much aggrevation to many
people. Regexp's which parse for regexp literals in javascript, usually check
for '/' after chars which can not be followed by a div, eg (,[{ etc. I think a
regexp literal can occur almost anywhere a string literal can apart from this
restriction.

-- quote --

The source text of an ECMAScript program is first converted into a sequence of
input elements, which are either tokens, line terminators, comments, or white
space. The source text is scanned from left to right, repeatedly taking the
longest possible sequence of characters as the next input element.

There are two goal symbols for the lexical grammar. The InputElementDiv symbol
is used in those syntactic grammar contexts where a division (/) or
division-assignment (/=) operator is permitted. The InputElementRegExp symbol is
used in other syntactic grammar contexts.

Note that contexts exist in the syntactic grammar where both a division and a
RegularExpressionLiteral are permitted by the syntactic grammar; however, since
the lexical grammar uses the InputElementDiv goal symbol in such cases, the
opening slash is not recognised as starting a regular expression literal in such
a context. As a workaround, one may enclose the regular expression literal in p.
#@nonl
#@-node:ekr.20071211092214:Javascript spec
#@+node:ekr.20071027111225.2:class javaScriptScanner
# The syntax for patterns causes all kinds of problems...

class javaScriptScanner (baseScannerClass):

    @others
#@+node:ekr.20071027111225.3:javaScriptScanner.__init__
def __init__ (self,importCommands,atAuto):

    # Init the base class.
    baseScannerClass.__init__(self,importCommands,atAuto=atAuto,language='java')
        # The langauge is used to set comment delims.

    # Set the parser delims.
    self.blockCommentDelim1 = '/*'
    self.blockCommentDelim2 = '*/'
    self.blockDelim1 = '{'
    self.blockDelim2 = '}'
    self.hasClasses = False
    self.hasFunctions = True
    self.lineCommentDelim = '//'
    self.lineCommentDelim2 = None
    self.outerBlockDelim1 = None # For now, ignore outer blocks.
    self.outerBlockDelim2 = None
    self.classTags = []
    self.functionTags = ['function']
    self.sigFailTokens = [';',] # ','=',] # Just like Java.
#@-node:ekr.20071027111225.3:javaScriptScanner.__init__
#@+node:ekr.20071102150937:startsString
def startsString(self,s,i):

    if g.match(s,i,'"') or g.match(s,i,"'"):
        # Count the number of preceding backslashes:
        n = 0 ; j = i-1
        while j >= 0 and s[j] == '\\':
            n += 1
            j -= 1
        return (n % 2) == 0
    elif g.match(s,i,'//'): ##  or g.match(s,i,'/\\'):
        # Neither of these are valid in regexp literals.
        return False
    elif g.match(s,i,'/'):
        # could be a division operator or regexp literal.
        while i >= 0 and s[i-1] in ' \t\n':
            i -= 1
        if i == 0: return True
        return s[i-1] in (',([{=')
    else:
        return False
#@-node:ekr.20071102150937:startsString
#@+node:ekr.20071102161115:skipString
def skipString (self,s,i):

    # Returns len(s) on unterminated string.
    if s[i] in ('"',"'"):
        return g.skip_string(s,i,verbose=False)
    else:
        # Match a regexp pattern.
        delim = '/'
        assert(s[i] == delim)
        i += 1
        n = len(s)
        while i < n:
            if s[i] == delim and s[i-1] != '\\':
                # This ignores flags, but does that matter?
                return i + 1
            else:
                i += 1
        return i
#@nonl
#@-node:ekr.20071102161115:skipString
#@-node:ekr.20071027111225.2:class javaScriptScanner
#@-node:ekr.20071027121956:Completed javascript import scanner
#@-node:ekr.20071211113202.2:New features
#@-node:ekr.20071217092725:Beta 1
#@+node:ekr.20071217092725.1:Beta 2
#@+node:ekr.20071219084047:Fixed bug in nav_buttons plugin
@nocolro

http://sourceforge.net/forum/message.php?msg_id=4682598
By: plumloco


In nav_buttons plugin when showing the recentSectionsDialog, delete and clearall
result in tk error dialogs.

The fix is to ...

change line 323 to: c.nodeHistory.clear()
change line 351 to: p = self.positionList[n]

#@-node:ekr.20071219084047:Fixed bug in nav_buttons plugin
#@+node:ekr.20071218110722:Fixed problems with modes/rest.py
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4681175
By: terry_n_brown

It seems the colorization dies shortly into each node. So the first .. comment
might be colored, or the first part of an `interpreted` word, but then it gives
up.

What I did:

- There was a horrendous bug in the match_seq_regexp matcher which could cause the colorizer to loop.
- There was another horrendous bug in match_regexp_helper that cause spurious matches.
- Added some more defensive code to have the colorizer recover more gracefully in this case. 
- The jedit2py script was not associating regexp rules whose regex starts with
  '\' with the proper character. The proper character is the hash_char
  character, not the '\' character. As a result, regexp's that start with '\'
  never got matched.

The following colorizers are affected by the change:

modes/apacheconf.py
modes/erlang.py
modes/moin.py
modes/perl.py
modes/php.py
modes/pl1.py
modes/rest.py
modes/shell.py
modes/shellscript.py
#@nonl
#@-node:ekr.20071218110722:Fixed problems with modes/rest.py
#@+node:ekr.20080105135724:Replaced delete-all-icons command with a script in script.leo
#@-node:ekr.20080105135724:Replaced delete-all-icons command with a script in script.leo
#@+node:ekr.20080105140645:Better messages if Image can not be imported
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4687706
By: derwisch

I inserted appropriate warning messages in the getImage function. It now
reads something like:

@color

    try:
        from PIL import Image
    except ImportError:
        g.es("could not import Image")
        Image = None

    try:
        from PIL import ImageTk
    except ImportError:
        try:
            import ImageTk
        except ImportError:
            g.es("could not import ImageTk")
            ImageTk = None
#@nonl
#@+node:ekr.20071114083142:getImage
def getImage (self,path):

    c = self.c

    try:
        from PIL import Image
    except ImportError:
        Image = None
        g.es('can not import Image module from PIL',color='blue')

    try:
        from PIL import ImageTk
    except ImportError:
        try:
            import ImageTk
        except ImportError:
            ImageTk = None
            g.es('can not import ImageTk module',color='blue')

    try:
        if Image and ImageTk:
            image1 = Image.open(path)
            image = ImageTk.PhotoImage(image1)
        else:
            import Tkinter as Tk
            image = Tk.PhotoImage(master=c.frame.tree.canvas,file=path)
        return image,image.height()
    except Exception:
        return None,None
#@-node:ekr.20071114083142:getImage
#@-node:ekr.20080105140645:Better messages if Image can not be imported
#@+node:ekr.20080105140204.1:Investigated icon path bug
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4688975
By: rui_curado

Another problem : When we work on a .leo file in different computers, in different
languages (I use US English Windows on one machine and Portuguese on another),
user icons will not work. I was not seeing any icons in the Portuguese Windows
computer.

On the US English Windows, Leo is installed in "C:\Program Files\Leo".
On the Portuguese Windows, Leo is installed in "C:\Programas\Leo".

But Leo is always assuming that the user library location is "C:\Program
Files\Leo\Icons".

@color
#@nonl
#@+node:ekr.20071114081313:icons...
@

To do:

- Define standard icons in a subfolder of Icons folder?
- Tree control recomputes height of each line.
#@+node:ekr.20080108092811: Helpers
#@+node:ekr.20080108091349:appendImageDictToList
def appendImageDictToList(self,aList,iconDir,path,xoffset,**kargs):

    c = self.c
    path = g.os_path_abspath(g.os_path_join(iconDir,path))
    relPath = g.makePathRelativeTo(path,iconDir)

    image,image_height = self.getImage(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:tbrown.20080119085249:getIconList
def getIconList(self, p):
    """Return list of icons for position p, call setIconList to apply changes"""

    fromTnode = []
    if hasattr(p.v.t,'unknownAttributes'):
        fromTnode = [dict(i) for i in p.v.t.unknownAttributes.get('icons',[])]
        for i in fromTnode: i['on'] = 'tnode'

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

    fromTnode.extend(fromVnode)
    return fromTnode
#@-node:tbrown.20080119085249:getIconList
#@+node:tbrown.20080119085249.1:setIconList
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"""

    # FIXME lineYOffset is expected to be on a tnode in drawing code

    if subl:
        if not hasattr(uaLoc,'unknownAttributes'):
            uaLoc.unknownAttributes = {}
        uaLoc.unknownAttributes['icons'] = list(subl)
        # g.es((p.headString(),uaLoc.unknownAttributes['icons']))
        uaLoc.unknownAttributes["lineYOffset"] = 3
        p.setDirty()
    else:
        if hasattr(uaLoc,'unknownAttributes'):
            if 'icons' in uaLoc.unknownAttributes:
                del uaLoc.unknownAttributes['icons']
                uaLoc.unknownAttributes["lineYOffset"] = 0
                p.setDirty()

def dHash(self, d):
    """Hash a dictionary"""
    l = d.keys()
    l.sort()
    return ''.join(['%s%s' % (str(k),str(d[k])) for k in l])

def setIconList(self, p, l):
    """Set list of icons for position p to l"""

    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 lHash == cHash:
        # no difference between original and current list of dictionaries
        return


    subl = [i for i in l if i.get('on') != 'vnode']
    self._setIconListHelper(p, subl, p.v.t)

    subl = [i for i in l if i.get('on') == 'vnode']
    self._setIconListHelper(p, subl, p.v)
#@-node:tbrown.20080119085249.1:setIconList
#@+node:ekr.20071114083142:getImage
def getImage (self,path):

    c = self.c

    try:
        from PIL import Image
    except ImportError:
        Image = None
        g.es('can not import Image module from PIL',color='blue')

    try:
        from PIL import ImageTk
    except ImportError:
        try:
            import ImageTk
        except ImportError:
            ImageTk = None
            g.es('can not import ImageTk module',color='blue')

    try:
        if Image and ImageTk:
            image1 = Image.open(path)
            image = ImageTk.PhotoImage(image1)
        else:
            import Tkinter as Tk
            image = Tk.PhotoImage(master=c.frame.tree.canvas,file=path)
        return image,image.height()
    except Exception:
        return None,None
#@-node:ekr.20071114083142:getImage
#@-node:ekr.20080108092811: Helpers
#@+node:ekr.20071114082418.2:deleteAllIcons (no longer used)
# def deleteAllIcons (self,event=None):

    # c = self.c

    # for p in c.allNodes_iter():

        # if hasattr(p.v.t,"unknownAttributes"):
            # a = p.v.t.unknownAttributes
            # iconsList = a.get("icons")
            # if iconsList:
                # a["icons"] = []
                # a["lineYOffset"] = 0
                # p.setDirty()
                # c.setChanged(True)

    # c.redraw()
#@-node:ekr.20071114082418.2:deleteAllIcons (no longer used)
#@+node:ekr.20071114082418:deleteFirstIcon
def deleteFirstIcon (self,event=None):

    c = self.c ; p = c.currentPosition()

    aList = self.getIconList(p)

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

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

    basePath = g.os_path_abspath(g.os_path_normpath(g.os_path_join(g.app.loadDir,"..","Icons")))
    absRelPath = g.os_path_abspath(g.os_path_normpath(g.os_path_join(basePath,relPath)))
    name = g.os_path_abspath(name)

    newList = []
    for d in aList:
        name2 = d.get('file')
        name2 = g.os_path_abspath(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()
    else:
        g.trace('not found',name)



#@-node:ekr.20071114092622:deleteIconByName
#@+node:ekr.20071114085054:deleteLastIcon
def deleteLastIcon (self,event=None):

    c = self.c ;  p = c.currentPosition()

    c = self.c ; p = c.currentPosition()

    aList = self.getIconList(p)

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

    c = self.c ; p = c.currentPosition()

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

    c = self.c ; p = c.currentPosition()

    iconDir = g.os_path_abspath(g.os_path_normpath(g.os_path_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()
#@-node:ekr.20071114081313.1:insertIcon
#@+node:ekr.20080108090719:insertIconFromFile
def insertIconFromFile (self,path,p=None,pos=None,**kargs):

    c = self.c
    if p is None: p = c.currentPosition()

    iconDir = g.os_path_abspath(g.os_path_normpath(g.os_path_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()
#@-node:ekr.20080108090719:insertIconFromFile
#@-node:ekr.20071114081313:icons...
#@+node:ekr.20071114113736:g.makePathRelativeTo
def makePathRelativeTo (fullPath,basePath):

    if fullPath.startswith(basePath):
        s = fullPath[len(basePath):]
        if s.startswith(os.path.sep):
            s = s[len(os.path.sep):]
        return s
    else:
        return fullPath
#@-node:ekr.20071114113736:g.makePathRelativeTo
#@+node:ekr.20071114081313.1:insertIcon
def insertIcon (self,event=None):

    c = self.c ; p = c.currentPosition()

    iconDir = g.os_path_abspath(g.os_path_normpath(g.os_path_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()
#@-node:ekr.20071114081313.1:insertIcon
#@-node:ekr.20080105140204.1:Investigated icon path bug
#@+node:ekr.20080108090642:Added convenience methods for icons
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4708221
By: derwisch

I would love to see a more polished version of icon handling where you can easily
add icons as part of scripts and not only by selecting menu items. At the moment,
I am not really getting how to untangle the file selection dialog from the insertion
of the icon into the headline.

@color
#@+node:ekr.20071114081313:icons...
@

To do:

- Define standard icons in a subfolder of Icons folder?
- Tree control recomputes height of each line.
#@+node:ekr.20080108092811: Helpers
#@+node:ekr.20080108091349:appendImageDictToList
def appendImageDictToList(self,aList,iconDir,path,xoffset,**kargs):

    c = self.c
    path = g.os_path_abspath(g.os_path_join(iconDir,path))
    relPath = g.makePathRelativeTo(path,iconDir)

    image,image_height = self.getImage(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:tbrown.20080119085249:getIconList
def getIconList(self, p):
    """Return list of icons for position p, call setIconList to apply changes"""

    fromTnode = []
    if hasattr(p.v.t,'unknownAttributes'):
        fromTnode = [dict(i) for i in p.v.t.unknownAttributes.get('icons',[])]
        for i in fromTnode: i['on'] = 'tnode'

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

    fromTnode.extend(fromVnode)
    return fromTnode
#@-node:tbrown.20080119085249:getIconList
#@+node:tbrown.20080119085249.1:setIconList
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"""

    # FIXME lineYOffset is expected to be on a tnode in drawing code

    if subl:
        if not hasattr(uaLoc,'unknownAttributes'):
            uaLoc.unknownAttributes = {}
        uaLoc.unknownAttributes['icons'] = list(subl)
        # g.es((p.headString(),uaLoc.unknownAttributes['icons']))
        uaLoc.unknownAttributes["lineYOffset"] = 3
        p.setDirty()
    else:
        if hasattr(uaLoc,'unknownAttributes'):
            if 'icons' in uaLoc.unknownAttributes:
                del uaLoc.unknownAttributes['icons']
                uaLoc.unknownAttributes["lineYOffset"] = 0
                p.setDirty()

def dHash(self, d):
    """Hash a dictionary"""
    l = d.keys()
    l.sort()
    return ''.join(['%s%s' % (str(k),str(d[k])) for k in l])

def setIconList(self, p, l):
    """Set list of icons for position p to l"""

    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 lHash == cHash:
        # no difference between original and current list of dictionaries
        return


    subl = [i for i in l if i.get('on') != 'vnode']
    self._setIconListHelper(p, subl, p.v.t)

    subl = [i for i in l if i.get('on') == 'vnode']
    self._setIconListHelper(p, subl, p.v)
#@-node:tbrown.20080119085249.1:setIconList
#@+node:ekr.20071114083142:getImage
def getImage (self,path):

    c = self.c

    try:
        from PIL import Image
    except ImportError:
        Image = None
        g.es('can not import Image module from PIL',color='blue')

    try:
        from PIL import ImageTk
    except ImportError:
        try:
            import ImageTk
        except ImportError:
            ImageTk = None
            g.es('can not import ImageTk module',color='blue')

    try:
        if Image and ImageTk:
            image1 = Image.open(path)
            image = ImageTk.PhotoImage(image1)
        else:
            import Tkinter as Tk
            image = Tk.PhotoImage(master=c.frame.tree.canvas,file=path)
        return image,image.height()
    except Exception:
        return None,None
#@-node:ekr.20071114083142:getImage
#@-node:ekr.20080108092811: Helpers
#@+node:ekr.20071114082418.2:deleteAllIcons (no longer used)
# def deleteAllIcons (self,event=None):

    # c = self.c

    # for p in c.allNodes_iter():

        # if hasattr(p.v.t,"unknownAttributes"):
            # a = p.v.t.unknownAttributes
            # iconsList = a.get("icons")
            # if iconsList:
                # a["icons"] = []
                # a["lineYOffset"] = 0
                # p.setDirty()
                # c.setChanged(True)

    # c.redraw()
#@-node:ekr.20071114082418.2:deleteAllIcons (no longer used)
#@+node:ekr.20071114082418:deleteFirstIcon
def deleteFirstIcon (self,event=None):

    c = self.c ; p = c.currentPosition()

    aList = self.getIconList(p)

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

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

    basePath = g.os_path_abspath(g.os_path_normpath(g.os_path_join(g.app.loadDir,"..","Icons")))
    absRelPath = g.os_path_abspath(g.os_path_normpath(g.os_path_join(basePath,relPath)))
    name = g.os_path_abspath(name)

    newList = []
    for d in aList:
        name2 = d.get('file')
        name2 = g.os_path_abspath(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()
    else:
        g.trace('not found',name)



#@-node:ekr.20071114092622:deleteIconByName
#@+node:ekr.20071114085054:deleteLastIcon
def deleteLastIcon (self,event=None):

    c = self.c ;  p = c.currentPosition()

    c = self.c ; p = c.currentPosition()

    aList = self.getIconList(p)

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

    c = self.c ; p = c.currentPosition()

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

    c = self.c ; p = c.currentPosition()

    iconDir = g.os_path_abspath(g.os_path_normpath(g.os_path_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()
#@-node:ekr.20071114081313.1:insertIcon
#@+node:ekr.20080108090719:insertIconFromFile
def insertIconFromFile (self,path,p=None,pos=None,**kargs):

    c = self.c
    if p is None: p = c.currentPosition()

    iconDir = g.os_path_abspath(g.os_path_normpath(g.os_path_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()
#@-node:ekr.20080108090719:insertIconFromFile
#@-node:ekr.20071114081313:icons...
#@-node:ekr.20080108090642:Added convenience methods for icons
#@+node:ekr.20080110050340:Fixed undo bug
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4712329
By: hibou-57

I think I've found a little bug in Leo.

It can be reproduce as follow :
- Create a leo file, containing file @file node (anything you want).
- Create a content for this @file node
- Save, to record the leo file, and to tangle the target file
- Modify something in the @file node content
- Save again : the leo file is recorded and the target file is retangled (Ok)
- Now, do a Ctrl-Z (undo command)
- Save : the leo file is recorded (Ok) but the target file is not retangled
(the lack)

@color
#@nonl
#@+node:ekr.20040303163330:p.setDirty
def setDirty (self,setDescendentsDirty=True):

    '''Mark a node and all ancestor @file nodes dirty.'''

    p = self ; dirtyVnodeList = []

    # g.trace(p.headString(),g.callers())

    if not p.v.t.isDirty():
        p.v.t.setDirty()
        dirtyVnodeList.append(p.v)

    # Important: this must be called even if p.v is already dirty.
    # Typing can change the @ignore state!
    dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty(setDescendentsDirty)
    dirtyVnodeList.extend(dirtyVnodeList2)

    return dirtyVnodeList
#@-node:ekr.20040303163330:p.setDirty
#@+node:ekr.20040303214038:p.setAllAncestorAtFileNodesDirty
def setAllAncestorAtFileNodesDirty (self,setDescendentsDirty=False):

    p = self
    dirtyVnodeList = []

    # Calculate all nodes that are joined to p or parents of such nodes.
    nodes = p.findAllPotentiallyDirtyNodes()

    if setDescendentsDirty:
        # N.B. Only mark _direct_ descendents of nodes.
        # Using the findAllPotentiallyDirtyNodes algorithm would mark way too many nodes.
        for p2 in p.subtree_iter():
            # Only @thin nodes need to be marked.
            if p2.v not in nodes and p2.isAtThinFileNode():
                nodes.append(p2.v)

    dirtyVnodeList = [v for v in nodes
        if not v.t.isDirty() and v.isAnyAtFileNode()]
    changed = len(dirtyVnodeList) > 0

    for v in dirtyVnodeList:
        v.t.setDirty() # Do not call v.setDirty here!

    return dirtyVnodeList
#@nonl
#@-node:ekr.20040303214038:p.setAllAncestorAtFileNodesDirty
#@+node:ekr.20050410095424:updateMarks
def updateMarks (self,oldOrNew):

    '''Update dirty and marked bits.'''

    u = self ; c = u.c

    if oldOrNew not in ('new','old'):
        g.trace("can't happen")
        return

    isOld = oldOrNew=='old'
    marked = g.choose(isOld,u.oldMarked, u.newMarked)

    if marked:  c.setMarked(u.p)
    else:       c.clearMarked(u.p)

    # Bug fix: Leo 4.4.6: Undo/redo always set changed/dirty bits
    # because the file may have been saved.
    u.p.setDirty(setDescendentsDirty=False)
    u.p.setAllAncestorAtFileNodesDirty(setDescendentsDirty=False) # Bug fix: Leo 4.4.6
    u.c.setChanged(True)
#@-node:ekr.20050410095424:updateMarks
#@-node:ekr.20080110050340:Fixed undo bug
#@+node:ekr.20080111083955.1:Don't mark nodes dirty if insert-icon is cancelled
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4714779
By: terry_n_brown

dirties?  I guess that's the verb.  Anyway, extremely trivial, but the insert
icon menu command marks the node as changed even if you click cancel and don't
insert any icon.


@color
#@nonl
#@+node:ekr.20071114081313.1:insertIcon
def insertIcon (self,event=None):

    c = self.c ; p = c.currentPosition()

    iconDir = g.os_path_abspath(g.os_path_normpath(g.os_path_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()
#@-node:ekr.20071114081313.1:insertIcon
#@-node:ekr.20080111083955.1:Don't mark nodes dirty if insert-icon is cancelled
#@+node:ekr.20080111090250:Removed unit tests that depend on files not on cvs
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4713900
By: plumloco

All unit tests pass on Windows except for three missing files, which were missing
pre-commit.

Windows Errors.


ERROR: @test c# ref card
IOError: [Errno 2] No such file or directory:
u'E:\\leo\\leo\\test\\big-c#-test.c#'

ERROR: @test constants.java
IOError: [Errno 2] No such file or directory:
u'E:\\leo\\leo\\test\\constants.java'

ERROR: @test AdminPermission.java
IOError: [Errno 2] No such file or directory:
u'E:\\leo\\leo\\test\\AdminPermission.java'

On Linux I get dozens  of errors of the form:

======================================================================
ERROR: Test syntax and tabbing of <<various plugin names>> plugin
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 33, in runTest
  File "/home/bob/work/leo/leo/src/leoTest.py", line 1601, in checkFileSyntax
    compiler.parse(s + '\n')
  File "compiler/transformer.py", line 52, in parse
  File "compiler/transformer.py", line 129, in parsesuite
  File "<string>", line 3

    ^
SyntaxError: invalid syntax

@color
#@nonl
#@+node:ekr.20051104075904.93:checkFileSyntax
def checkFileSyntax (fileName,s):

    try:
        compiler.parse(s + '\n')
    except SyntaxError:
        g.es("syntax error in:",fileName,color="blue")
        g.es_exception(full=False,color="black")
        raise
#@-node:ekr.20051104075904.93:checkFileSyntax
#@-node:ekr.20080111090250:Removed unit tests that depend on files not on cvs
#@-node:ekr.20071217092725.1:Beta 2
#@-node:ekr.20071211113202:4.4.6
#@+node:ekr.20080211100700:4.4.7
#@+node:ekr.20080214083121:beta 1
#@+node:ekr.20080210205942:Fixed bugs
#@+node:ekr.20080210201503:Fixed perl syntax coloring bug
@nocolor

The rexepx's in perl_rule23 and per_rule24 are very complicated and cause re.match to hang.
#@nonl
#@+node:ekr.20080208105909.2:Email
@nocolor

I've narrowed the Perl errors down to an incredibly small Perl function --- I tried to debug myself, but couldn't immediately find where this was hanging up (or where to set reasonable breakpoints).

Attached is the Perl errant file with the perl.

To see the problem:

    * open the leo file;
    * select node "APerlProblem"=>foo
    * note @language text
    * select node "APerlProblem"=>foo=>problem.pl
    * Note no colorization
    * in "A Perl Problem"=>foo  change @language text to @language perl
    * note: you can save the Leo file fine;  you can also navigate to the boy of "just text" node
    * navigate to the body of node  APerlProblem=>foo=>ok.pl - it is colorized, and "ok".
    * NOTE: when you navigate to problem.pl node, Leo hangs

I added the OK.pl node just to convince myself - it hangs with just the "problem.pl" node just as well.
#@nonl
#@-node:ekr.20080208105909.2:Email
#@-node:ekr.20080210201503:Fixed perl syntax coloring bug
#@+node:ekr.20080131082239:Removed warnings while typing @language
@nocolor

http://sourceforge.net/forum/message.php?msg_id=4751122
By: montanaro

I had a node which looked like this:

    import sys

I wanted to add an @language python directive, so I positioned the
cursor in front of the "i" and started typing.  I typed "@language "
and was left with this:

    @language import sys

It complained about not being able to import "import".  Next I typed
"p", leaving

    @language pimport sys

It complained again, this time about "pimport".  It seems to me that
Leo ought to sit back and watch the typing without complaint until I
type a character not in the acceptible set for the @language directive
(typically a SPC, RET or punctuation).  I can see that it seems to
only make the same complaint once (type "p", get a complaint about
"pimport", delete "p", type "p" again, no complaint this time), but
just like an interactive spell checker waits until the user thinks
he's typed a word, maybe Leo should wait as well.
#@-node:ekr.20080131082239:Removed warnings while typing @language
#@+node:ekr.20080208104232:Renamed execute-ipython-script to push-to-ipython
@nocolor

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

- rename 'execute-ipython-script' to 'push-to-ipython'

Rationale: I see this as a general command that requests leo to
interact with ipython for current node. The default functionality
would of course be to execute the script, but there will be
context-sensitive functionality later on.

For example, If a node had the text

@ipy-eval foo

... some stuff
@ipy-eval

This would evaluate 'foo' in IPython and fill the node (from the line
'@ipy-eval foo' downwards, up to @ipy-eval, replacing '... some
stuff') with the result of the evaluation of foo.

- instead of the current pre-created @ipython-results, this would be more handy:

Headline @ipy-var foo

would mean the contents of ipython variable foo.

When we do

leox.results.foo = "hello"

We would just scan the tree for @ipy-var foo, and then fill up that
node. If such a node is not found, just create it as the last child of
the root node. That way, we don't need any pre-created nodes in the
tree. Of course 'results' is a misnomer then...
#@-node:ekr.20080208104232:Renamed execute-ipython-script to push-to-ipython
#@+node:ekr.20080201092518:All import commands now honor @path
@nocolor
http://sourceforge.net/forum/message.php?msg_id=4752044
By: ktenney

I'm finding that an @auto file.py
node that's a child of a node with headString
@path /my/path/to/the/project/directory
fails to load /my/path/to/the/project/directory/file.py
when I do
File->Read Write->Read @auto nodes

@color
#@nonl
#@+node:ekr.20031218072017.3210:createOutline (leoImport)
def createOutline (self,fileName,parent,atAuto=False,s=None,ext=None):

    c = self.c ; u = c.undoer ; s1 = s

    # New in Leo 4.4.7: honor @path directives.

    self.scanDefaultDirectory(parent) # sets .defaultDirectory.
    filename = g.os_path_join(self.default_directory,fileName)
    junk,self.fileName = g.os_path_split(fileName)
    self.methodName,self.fileType = g.os_path_splitext(self.fileName)
    self.setEncoding(p=parent,atAuto=atAuto)
    # g.trace(self.fileName,self.fileType)
    # All file types except the following just get copied to the parent node.
    if not ext: ext = self.fileType
    ext = ext.lower()
    if not s:
        << Read file into s >>

    << convert s to the proper encoding >>

    # Create the top-level headline.
    if atAuto:
        p = parent.copy()
        c.beginUpdate()
        try:
            p.setTnodeText('')
        finally:
            c.endUpdate(False)
    else:
        undoData = u.beforeInsertNode(parent)
        p = parent.insertAsLastChild()
        if self.treeType == "@file":
            p.initHeadString("@file " + fileName)
        else:
            # @root nodes don't have @root in the headline.
            p.initHeadString(fileName)
        u.afterInsertNode(p,'Import',undoData)

    self.rootLine = g.choose(self.treeType=="@file","","@root-code "+self.fileName+'\n')

    if ext in (".c", ".cpp", ".cxx"):
        self.scanCText(s,p,atAuto=atAuto)
    elif ext == '.c#':
        self.scanCSharpText(s,p,atAuto=atAuto)
    elif ext == ".el":
        self.scanElispText(s,p,atAuto=atAuto)
    elif ext == ".java":
        self.scanJavaText(s,p,atAuto=atAuto)
    elif ext == ".js":
        self.scanJavaScriptText(s,p,atAuto=atAuto)
    elif ext == ".pas":
        self.scanPascalText(s,p,atAuto=atAuto)
    elif ext in (".py", ".pyw"):
        self.scanPythonText(s,p,atAuto=atAuto)
    elif ext == ".php":
        self.scanPHPText(s,p,atAuto=atAuto)
    elif ext in ('.html','.htm','.xml'):
        self.scanXmlText(s,p,atAuto=atAuto)
    else:
        self.scanUnknownFileType(s,p,ext,atAuto=atAuto)

    p.contract()
    return p
#@+node:ekr.20031218072017.3211:<< Read file into s >>
try:
    fileName = g.os_path_normpath(fileName)
    theFile = open(fileName)
    s = theFile.read()
    theFile.close()
except IOError:
    z = g.choose(atAuto,'@auto ','')
    g.es("can not open", "%s%s" % (z,fileName),color='red')
    leoTest.fail()
    return None
#@-node:ekr.20031218072017.3211:<< Read file into s >>
#@+node:ekr.20080212092908:<< convert s to the proper encoding >>
if s and fileName.endswith('.py'):
    # Python's encoding comments override everything else.
    lines = g.splitLines(s)
    tag = '# -*- coding:' ; tag2 = '-*-'
    n1,n2 = len(tag),len(tag2)
    line1 = lines[0].strip()
    if line1.startswith(tag) and line1.endswith(tag2):
        e = line1[n1:-n2].strip()
        if e and g.isValidEncoding(e):
            # print 'found',e,'in',line1
            self.encoding = e

s = g.toUnicode(s,self.encoding)
#@-node:ekr.20080212092908:<< convert s to the proper encoding >>
#@-node:ekr.20031218072017.3210:createOutline (leoImport)
#@+node:ekr.20080211085914:scanDefaultDirectory (leoImport)
def scanDefaultDirectory(self,p):

    """Set .default_directory by looking for @path directives."""

    c = self.c
    self.default_directory = None
    << Set path from @file node >>
    if self.default_directory:
        return

    for p in p.self_and_parents_iter():
        theDict = g.get_directives_dict(p)
        if theDict.has_key("path"):
            << handle @path >>
            return

    << Set current directory >>
    if not self.default_directory:
        # This should never happen: c.openDirectory should be a good last resort.
        self.error("No absolute directory specified anywhere.")
        self.default_directory = ""
#@+node:ekr.20080211085914.1:<< Set path from @file node >>
# An absolute path in an @file node over-rides everything else.
# A relative path gets appended to the relative path by the open logic.

name = p.anyAtFileNodeName()

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

if theDir and g.os_path_isabs(theDir):
    if g.os_path_exists(theDir):
        self.default_directory = theDir
    else:
        self.default_directory = g.makeAllNonExistentDirectories(theDir,c=c)
        if not self.default_directory:
            self.error("Directory \"%s\" does not exist" % theDir)
#@-node:ekr.20080211085914.1:<< Set path from @file node >>
#@+node:ekr.20080211085914.2:<< handle @path >>
# We set the current director to a path so future writes will go to that directory.

path = theDict["path"]
path = g.computeRelativePath (path)

if path:
    base = g.getBaseDirectory(c) # returns "" on error.
    path = g.os_path_join(base,path)

    if g.os_path_isabs(path):
        << handle absolute path >>
    else:
        self.error("ignoring bad @path: %s" % path)
else:
    self.error("ignoring empty @path")
#@+node:ekr.20080211085914.3:<< handle absolute path >>
# path is an absolute path.

if g.os_path_exists(path):
    self.default_directory = path
else:
    self.default_directory = g.makeAllNonExistentDirectories(path,c=c)
    if not self.default_directory:
        self.error("invalid @path: %s" % path)
#@-node:ekr.20080211085914.3:<< handle absolute path >>
#@-node:ekr.20080211085914.2:<< handle @path >>
#@+node:ekr.20080211085914.4:<< Set current directory >>
# This code is executed if no valid absolute path was specified in the @file node or in an @path directive.

assert(not self.default_directory)

if c.frame :
    base = g.getBaseDirectory(c) # returns "" on error.
    for theDir in (c.tangle_directory,c.frame.openDirectory,c.openDirectory):
        if theDir and len(theDir) > 0:
            theDir = g.os_path_join(base,theDir)
            if g.os_path_isabs(theDir): # Errors may result in relative or invalid path.
                if g.os_path_exists(theDir):
                    self.default_directory = theDir ; break
                else:
                    self.default_directory = g.makeAllNonExistentDirectories(theDir,c=c)
#@-node:ekr.20080211085914.4:<< Set current directory >>
#@-node:ekr.20080211085914:scanDefaultDirectory (leoImport)
#@-node:ekr.20080201092518:All import commands now honor @path
#@+node:ekr.20080208104232.1:Fixed pascal import bug
#@+node:ekr.20080210202354:Report
@nocolor

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

What I did:

- Added  .blockCommentDelim1_2 and .blockCommentDelim2_2 and associated logic
  to handle { and } as well as (* and *) as comment delims.

- Added .anonymousClasses ivar to base class.

- Added .blockDelim2Cruft ivar to base class, and associated code in skipBlock.

- Added all the new overrides to the pascalScanner class.
  These are needed to handle Delphi interface declarations.

- Hacked startsHelper in the base class to handle some new special cases.
#@nonl
#@-node:ekr.20080210202354:Report
#@+node:ekr.20070711104241.3:class pascalScanner
class pascalScanner (baseScannerClass):

    @others
#@nonl
#@+node:ekr.20080211065754:skipArgs
def skipArgs (self,s,i,kind):

    '''Skip the argument or class list.  Return i, ok

    kind is in ('class','function')'''

    # Pascal interfaces have no argument list.
    if kind == 'class':
        return i, True

    start = i
    i = g.skip_ws_and_nl(s,i)
    if not g.match(s,i,'('):
        return start,kind == 'class'

    i = self.skipParens(s,i)
    # skipParens skips the ')'
    if i >= len(s):
        return start,False
    else:
        return i,True 
#@-node:ekr.20080211065754:skipArgs
#@+node:ekr.20080211065906:ctor
def __init__ (self,importCommands,atAuto):

    # Init the base class.
    baseScannerClass.__init__(self,importCommands,atAuto=atAuto,language='pascal')

    # Set the parser overrides.
    self.anonymousClasses = ['interface']
    self.blockCommentDelim1 = '(*'
    self.blockCommentDelim1_2 = '{'
    self.blockCommentDelim2 = '*)'
    self.blockCommentDelim2_2 = '}'
    self.blockDelim1 = 'begin'
    self.blockDelim2 = 'end'
    self.blockDelim2Cruft = [';','.'] # For Delphi.
    self.classTags = ['interface']
    self.functionTags = ['function','procedure','constructor','destructor',]
    self.hasClasses = True
    self.lineCommentDelim = '//'
    self.strict = False
#@-node:ekr.20080211065906:ctor
#@+node:ekr.20080211070816:skipCodeBlock
def skipCodeBlock (self,s,i,kind):

    '''Skip the code block in a function or class definition.'''

    trace = False
    start = i

    if kind == 'class':
        i = self.skipInterface(s,i)
    else:
        i = self.skipBlock(s,i,delim1=None,delim2=None)

        if self.sigFailTokens:
            i = g.skip_ws(s,i)
            for z in self.sigFailTokens:
                if g.match(s,i,z):
                    if trace: g.trace('failtoken',z)
                    return start,False

    if i > start:
        i = self.skipNewline(s,i,kind)

    if trace:
        g.trace(g.callers())
        g.trace('returns...\n',g.listToString(g.splitLines(s[start:i])))

    return i,True
#@-node:ekr.20080211070816:skipCodeBlock
#@+node:ekr.20080211070945:skipInterface
def skipInterface(self,s,i):

    '''Skip from the opening delim to *past* the matching closing delim.

    If no matching is found i is set to len(s)'''

    trace = False
    start = i
    delim2 = 'end.'
    level = 0 ; start = i
    startIndent = self.startSigIndent
    if trace: g.trace('***','startIndent',startIndent,g.callers())
    while i < len(s):
        progress = i
        if g.is_nl(s,i):
            backslashNewline = i > 0 and g.match(s,i-1,'\\\n')
            i = g.skip_nl(s,i)
            if not backslashNewline and not g.is_nl(s,i):
                j, indent = g.skip_leading_ws_with_indent(s,i,self.tab_width)
                line = g.get_line(s,j)
                if trace: g.trace('indent',indent,line)
                if indent < startIndent and line.strip():
                    # An non-empty underindented line.
                    # Issue an error unless it contains just the closing bracket.
                    if level == 1 and g.match(s,j,delim2):
                        pass
                    else:
                        if j not in self.errorLines: # No error yet given.
                            self.errorLines.append(j)
                            self.underindentedLine(line)
        elif s[i] in (' ','\t',):
            i += 1 # speed up the scan.
        elif self.startsComment(s,i):
            i = self.skipComment(s,i)
        elif self.startsString(s,i):
            i = self.skipString(s,i)
        elif g.match(s,i,delim2):
            i += len(delim2)
            if trace: g.trace('returns\n',repr(s[start:i]))
            return i

        else: i += 1
        assert progress < i

    self.error('no interface')
    if 1:
        print '** no interface **'
        i,j = g.getLine(s,start)
        g.trace(i,s[i:j])
    else:
        if trace: g.trace('** no interface')
    return start
#@-node:ekr.20080211070945:skipInterface
#@+node:ekr.20080211070056:skipSigTail
def skipSigTail(self,s,i,kind):

    '''Skip from the end of the arg list to the start of the block.'''

    trace = False and self.trace

    # Pascal interface has no tail.
    if kind == 'class':
        return i,True

    start = i
    i = g.skip_ws(s,i)
    for z in self.sigFailTokens:
        if g.match(s,i,z):
            if trace: g.trace('failToken',z,'line',g.skip_line(s,i))
            return i,False
    while i < len(s):
        if self.startsComment(s,i):
            i = self.skipComment(s,i)
        elif g.match(s,i,self.blockDelim1):
            if trace: g.trace(repr(s[start:i]))
            return i,True
        else:
            i += 1
    if trace: g.trace('no block delim')
    return i,False
#@-node:ekr.20080211070056:skipSigTail
#@+node:ekr.20080211071959:putClass & helpers
def putClass (self,s,i,sigEnd,codeEnd,start,parent):

    '''Create a node containing the entire interface.'''

    # Enter a new class 1: save the old class info.
    oldMethodName = self.methodName
    oldStartSigIndent = self.startSigIndent

    # Enter a new class 2: init the new class info.
    self.indentRefFlag = None

    class_kind = self.classId
    class_name = self.sigId
    headline = '%s %s' % (class_kind,class_name)
    headline = headline.strip()
    self.methodName = headline

    # Compute the starting lines of the class.
    prefix = self.createClassNodePrefix()

    # Create the class node.
    class_node = self.createHeadline(parent,'',headline)

    # Put the entire interface in the body.
    result = s[start:codeEnd]
    self.appendTextToClassNode(class_node,result)

    # Exit the new class: restore the previous class info.
    self.methodName = oldMethodName
    self.startSigIndent = oldStartSigIndent
#@-node:ekr.20080211071959:putClass & helpers
#@-node:ekr.20070711104241.3:class pascalScanner
#@+node:ekr.20070712112008:startsHelper
def startsHelper(self,s,i,kind,tags):
    '''return True if s[i:] starts a class or function.
    Sets sigStart, sigEnd, sigId and codeEnd ivars.'''

    # if not tags: return False

    trace = False or self.trace
    verbose = False # kind=='function'
    self.codeEnd = self.sigEnd = self.sigId = None
    self.sigStart = i

    # Underindented lines can happen in any language, not just Python.
    # The skipBlock method of the base class checks for such lines.
    self.startSigIndent = self.getLeadingIndent(s,i)

    # Get the tag that starts the class or function.
    j = g.skip_ws_and_nl(s,i)
    i = self.skipId(s,j)
    self.sigId = theId = s[j:i] # Set sigId ivar 'early' for error messages.
    if not theId: return False

    if tags:
        if theId not in tags:
            if trace and verbose: g.trace('**** %s theId: %s not in tags: %s' % (kind,theId,tags))
            return False

    if trace and verbose: g.trace('kind',kind,'id',theId)

    # Get the class/function id.
    if kind == 'class' and self.sigId in self.anonymousClasses:
        # A hack for Delphi Pascal: interfaces have no id's.
        # g.trace('anonymous',self.sigId)
        classId = theId
        sigId = ''
    else:
        i, ids, classId = self.skipSigStart(s,j,kind,tags) # Rescan the first id.
        sigId = self.getSigId(ids)
        if not sigId:
            if trace and verbose: g.trace('**no sigId',g.get_line(s,i))
            return False

    if self.output_indent < self.startSigIndent:
        if trace: g.trace('**over-indent',sigId)
            #,'output_indent',self.output_indent,'startSigIndent',self.startSigIndent)
        return False

    # Skip the argument list.
    i, ok = self.skipArgs(s,i,kind)
    if not ok:
        if trace and verbose: g.trace('no args',g.get_line(s,i))
        return False
    i = g.skip_ws_and_nl(s,i)

    # Skip the tail of the signature
    i, ok = self.skipSigTail(s,i,kind)
    if not ok:
        if trace and verbose: g.trace('no tail',g.get_line(s,i))
        return False
    sigEnd = i

    # A trick: make sure the signature ends in a newline,
    # even if it overlaps the start of the block.
    if not g.match(s,sigEnd,'\n') and not g.match(s,sigEnd-1,'\n'):
        if trace and verbose: g.trace('extending sigEnd')
        sigEnd = g.skip_line(s,sigEnd)

    if self.blockDelim1:
        i = g.skip_ws_and_nl(s,i)
        if kind == 'class' and self.sigId in self.anonymousClasses:
            pass # Allow weird Pascal unit's.
        elif not g.match(s,i,self.blockDelim1):
            if trace and verbose: g.trace('no block',g.get_line(s,i))
            return False

    i,ok = self.skipCodeBlock(s,i,kind)
    if not ok: return False
        # skipCodeBlock skips the trailing delim.

    # Success: set the ivars.
    self.sigStart = self.adjustDefStart(s,self.sigStart)
    self.codeEnd = i
    self.sigEnd = sigEnd
    self.sigId = sigId
    self.classId = classId

    # Note: backing up here is safe because
    # we won't back up past scan's 'start' point.
    # Thus, characters will never be output twice.
    k = self.sigStart
    if not g.match(s,k,'\n'):
        self.sigStart = g.find_line_start(s,k)

    # Issue this warning only if we have a real class or function.
    if 0: ### wrong.
        if s[self.sigStart:k].strip():
            self.error('%s definition does not start a line\n%s' % (
                kind,g.get_line(s,k)))

    if trace: g.trace(kind,'returns\n'+s[self.sigStart:i])
    return True
#@-node:ekr.20070712112008:startsHelper
#@+node:ekr.20070707073859:skipBlock
def skipBlock(self,s,i,delim1=None,delim2=None):

    '''Skip from the opening delim to *past* the matching closing delim.

    If no matching is found i is set to len(s)'''

    trace = False
    start = i
    if delim1 is None: delim1 = self.blockDelim1
    if delim2 is None: delim2 = self.blockDelim2
    match1 = g.choose(len(delim1)==1,g.match,g.match_word)
    match2 = g.choose(len(delim2)==1,g.match,g.match_word)
    assert match1(s,i,delim1)
    level = 0 ; start = i
    startIndent = self.startSigIndent
    if trace: g.trace('***','startIndent',startIndent,g.callers())
    while i < len(s):
        progress = i
        if g.is_nl(s,i):
            backslashNewline = i > 0 and g.match(s,i-1,'\\\n')
            i = g.skip_nl(s,i)
            if not backslashNewline and not g.is_nl(s,i):
                j, indent = g.skip_leading_ws_with_indent(s,i,self.tab_width)
                line = g.get_line(s,j)
                if trace: g.trace('indent',indent,line)
                if indent < startIndent and line.strip():
                    # An non-empty underindented line.
                    # Issue an error unless it contains just the closing bracket.
                    if level == 1 and match2(s,j,delim2):
                        pass
                    else:
                        if j not in self.errorLines: # No error yet given.
                            self.errorLines.append(j)
                            self.underindentedLine(line)
        elif s[i] in (' ','\t',):
            i += 1 # speed up the scan.
        elif self.startsComment(s,i):
            i = self.skipComment(s,i)
        elif self.startsString(s,i):
            i = self.skipString(s,i)
        elif match1(s,i,delim1):
            level += 1 ; i += len(delim1)
        elif match2(s,i,delim2):
            level -= 1 ; i += len(delim2)
            # Skip junk following Pascal 'end'
            for z in self.blockDelim2Cruft:
                i2 = g.skip_ws(s,i)
                if g.match(s,i2,z):
                    i = i2 + len(z)
                    break
            if level <= 0:
                if trace: g.trace('returns\n',repr(s[start:i]))
                return i

        else: i += 1
        assert progress < i

    self.error('no block')
    if 1:
        print '** no block **'
        i,j = g.getLine(s,start)
        g.trace(i,s[i:j])
    else:
        if trace: g.trace('** no block')
    return start
#@-node:ekr.20070707073859:skipBlock
#@-node:ekr.20080208104232.1:Fixed pascal import bug
#@+node:ekr.20080210201527:Fixed import problems with django
#@+node:ekr.20080212083727:Report
@nocolor

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

I've just started dabbling w/ Django, and so I tried to bring up one "you figure
it out" app, so naturally thought to pull it into Leo...

Importing works (e.g. the app still works at the bringup stage). 

However, one of the dependent plugins has a test file which causes Leo heartburn. 

tests.py - is file which is essentially just one long docstring.  
Importing creates a "tests settings" node. 
I change the @file node above it to a @thin node. 
On writing, Leo errors out and writes the node in question internally. 

This is from http://django-tagging.googlecode.com/svn/trunk (or zip on http://code.google.com/p/django-tagging/) 

Copying the created child node up into the "@thin tests/tests.py" gets rid of the problem. 

Let me know if you want more info (the error is essentially the length of a long
docstring, so not sure if it's useful out of context).

Yarko
#@-node:ekr.20080212083727:Report
#@+node:ekr.20080212042248:What I did
@nocolor

Cvs now contains code to handle encodings properly for @auto files:

When reading an @auto tree, createOutline now does the following:

1. When importing Python files, createOutline uses the encoding implied by
Python's # -*- coding line. This line must be the first line of the imported file.

2. Otherwise, createOutline scans the @auto node and its ancestors for Leo's @encoding directive.

3. If no such @encoding directive is found, createOutline uses the encoding specified by the setting: @string default_at_auto_file_encoding = utf-8

4. If this setting does not exist, the utf-8 encoding is used.
#@-node:ekr.20080212042248:What I did
#@+node:ekr.20080212031914:tests.py
# -*- coding: utf-8 -*-
r"""
>>> import os
>>> from django import newforms as forms
>>> from tagging.forms import TagField
>>> from tagging import settings
>>> from tagging.models import Tag, TaggedItem
>>> from tagging.tests.models import Article, Link, Perch, Parrot, FormTest
>>> from tagging.utils import calculate_cloud, get_tag_list, get_tag, parse_tag_input
>>> from tagging.utils import LINEAR
>>> from tagging.validators import isTagList, isTag

#############
# Utilities #
#############

# Tag input ###################################################################

# Simple space-delimited tags
>>> parse_tag_input('one')
[u'one']
>>> parse_tag_input('one two')
[u'one', u'two']
>>> parse_tag_input('one two three')
[u'one', u'three', u'two']
>>> parse_tag_input('one one two two')
[u'one', u'two']

# Comma-delimited multiple words - an unquoted comma in the input will trigger
# this.
>>> parse_tag_input(',one')
[u'one']
>>> parse_tag_input(',one two')
[u'one two']
>>> parse_tag_input(',one two three')
[u'one two three']
>>> parse_tag_input('a-one, a-two and a-three')
[u'a-one', u'a-two and a-three']

# Double-quoted multiple words - a completed quote will trigger this.
# Unclosed quotes are ignored.
>>> parse_tag_input('"one')
[u'one']
>>> parse_tag_input('"one two')
[u'one', u'two']
>>> parse_tag_input('"one two three')
[u'one', u'three', u'two']
>>> parse_tag_input('"one two"')
[u'one two']
>>> parse_tag_input('a-one "a-two and a-three"')
[u'a-one', u'a-two and a-three']

# No loose commas - split on spaces
>>> parse_tag_input('one two "thr,ee"')
[u'one', u'thr,ee', u'two']

# Loose commas - split on commas
>>> parse_tag_input('"one", two three')
[u'one', u'two three']

# Double quotes can contain commas
>>> parse_tag_input('a-one "a-two, and a-three"')
[u'a-one', u'a-two, and a-three']
>>> parse_tag_input('"two", one, one, two, "one"')
[u'one', u'two']

# Bad users! Naughty users!
>>> parse_tag_input(None)
[]
>>> parse_tag_input('')
[]
>>> parse_tag_input('"')
[]
>>> parse_tag_input('""')
[]
>>> parse_tag_input('"' * 7)
[]
>>> parse_tag_input(',,,,,,')
[]
>>> parse_tag_input('",",",",",",","')
[u',']
>>> parse_tag_input('a-one "a-two" and "a-three')
[u'a-one', u'a-three', u'a-two', u'and']

# Normalised Tag list input ###################################################
>>> cheese = Tag.objects.create(name='cheese')
>>> toast = Tag.objects.create(name='toast')
>>> get_tag_list(cheese)
[<Tag: cheese>]
>>> get_tag_list('cheese toast')
[<Tag: cheese>, <Tag: toast>]
>>> get_tag_list('cheese,toast')
[<Tag: cheese>, <Tag: toast>]
>>> get_tag_list([])
[]
>>> get_tag_list(['cheese', 'toast'])
[<Tag: cheese>, <Tag: toast>]
>>> get_tag_list([cheese.id, toast.id])
[<Tag: cheese>, <Tag: toast>]
>>> get_tag_list(['cheese', 'toast', 'ŠĐĆŽćžšđ'])
[<Tag: cheese>, <Tag: toast>]
>>> get_tag_list([cheese, toast])
[<Tag: cheese>, <Tag: toast>]
>>> get_tag_list((cheese, toast))
(<Tag: cheese>, <Tag: toast>)
>>> get_tag_list(Tag.objects.filter(name__in=['cheese', 'toast']))
[<Tag: cheese>, <Tag: toast>]
>>> get_tag_list(['cheese', toast])
Traceback (most recent call last):
    ...
ValueError: If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids.
>>> get_tag_list(29)
Traceback (most recent call last):
    ...
ValueError: The tag input given was invalid.

# Normalised Tag input
>>> get_tag(cheese)
<Tag: cheese>
>>> get_tag('cheese')
<Tag: cheese>
>>> get_tag(cheese.id)
<Tag: cheese>
>>> get_tag('mouse')

# Tag clouds ##################################################################
>>> tags = []
>>> for line in open(os.path.join(os.path.dirname(__file__), 'tags.txt')).readlines():
...     name, count = line.rstrip().split()
...     tag = Tag(name=name)
...     tag.count = int(count)
...     tags.append(tag)

>>> sizes = {}
>>> for tag in calculate_cloud(tags, steps=5):
...     sizes[tag.font_size] = sizes.get(tag.font_size, 0) + 1

# This isn't a pre-calculated test, just making sure it's consistent
>>> sizes
{1: 48, 2: 30, 3: 19, 4: 15, 5: 10}

>>> sizes = {}
>>> for tag in calculate_cloud(tags, steps=5, distribution=LINEAR):
...     sizes[tag.font_size] = sizes.get(tag.font_size, 0) + 1

# This isn't a pre-calculated test, just making sure it's consistent
>>> sizes
{1: 97, 2: 12, 3: 7, 4: 2, 5: 4}

>>> calculate_cloud(tags, steps=5, distribution='cheese')
Traceback (most recent call last):
    ...
ValueError: Invalid distribution algorithm specified: cheese.

# Validators ##################################################################

>>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {})
Traceback (most recent call last):
    ...
ValidationError: [u'Each tag may be no more than 50 characters long.']

>>> isTag('"test"', {})
>>> isTag(',test', {})
>>> isTag('f o o', {})
Traceback (most recent call last):
    ...
ValidationError: [u'Multiple tags were given.']
>>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {})
Traceback (most recent call last):
    ...
ValidationError: [u'Each tag may be no more than 50 characters long.']

###########
# Tagging #
###########

# Basic tagging ###############################################################

>>> dead = Parrot.objects.create(state='dead')
>>> Tag.objects.update_tags(dead, 'foo,bar,"ter"')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: foo>, <Tag: ter>]
>>> Tag.objects.update_tags(dead, '"foo" bar "baz"')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: baz>, <Tag: foo>]
>>> Tag.objects.add_tag(dead, 'foo')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: baz>, <Tag: foo>]
>>> Tag.objects.add_tag(dead, 'zip')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: baz>, <Tag: foo>, <Tag: zip>]
>>> Tag.objects.add_tag(dead, '    ')
Traceback (most recent call last):
    ...
AttributeError: No tags were given: "    ".
>>> Tag.objects.add_tag(dead, 'one two')
Traceback (most recent call last):
    ...
AttributeError: Multiple tags were given: "one two".

# Note that doctest in Python 2.4 (and maybe 2.5?) doesn't support non-ascii
# characters in output, so we're displaying the repr() here.
>>> Tag.objects.update_tags(dead, 'ŠĐĆŽćžšđ')
>>> repr(Tag.objects.get_for_object(dead))
'[<Tag: \xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91>]'

>>> Tag.objects.update_tags(dead, None)
>>> Tag.objects.get_for_object(dead)
[]

# Using a model's TagField
>>> f1 = FormTest.objects.create(tags=u'test3 test2 test1')
>>> Tag.objects.get_for_object(f1)
[<Tag: test1>, <Tag: test2>, <Tag: test3>]
>>> f1.tags = u'test4'
>>> f1.save()
>>> Tag.objects.get_for_object(f1)
[<Tag: test4>]
>>> f1.tags = ''
>>> f1.save()
>>> Tag.objects.get_for_object(f1)
[]

# Forcing tags to lowercase
>>> settings.FORCE_LOWERCASE_TAGS = True
>>> Tag.objects.update_tags(dead, 'foO bAr Ter')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: foo>, <Tag: ter>]
>>> Tag.objects.update_tags(dead, 'foO bAr baZ')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: baz>, <Tag: foo>]
>>> Tag.objects.add_tag(dead, 'FOO')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: baz>, <Tag: foo>]
>>> Tag.objects.add_tag(dead, 'Zip')
>>> Tag.objects.get_for_object(dead)
[<Tag: bar>, <Tag: baz>, <Tag: foo>, <Tag: zip>]
>>> Tag.objects.update_tags(dead, None)
>>> f1.tags = u'TEST5'
>>> f1.save()
>>> Tag.objects.get_for_object(f1)
[<Tag: test5>]
>>> f1.tags
u'test5'

# Retrieving tags by Model ####################################################

>>> Tag.objects.usage_for_model(Parrot)
[]
>>> parrot_details = (
...     ('pining for the fjords', 9, True,  'foo bar'),
...     ('passed on',             6, False, 'bar baz ter'),
...     ('no more',               4, True,  'foo ter'),
...     ('late',                  2, False, 'bar ter'),
... )

>>> for state, perch_size, perch_smelly, tags in parrot_details:
...     perch = Perch.objects.create(size=perch_size, smelly=perch_smelly)
...     parrot = Parrot.objects.create(state=state, perch=perch)
...     Tag.objects.update_tags(parrot, tags)

>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True)]
[(u'bar', 3), (u'baz', 1), (u'foo', 2), (u'ter', 3)]
>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, min_count=2)]
[(u'bar', 3), (u'foo', 2), (u'ter', 3)]

# Limiting results to a subset of the model
>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state='no more'))]
[(u'foo', 1), (u'ter', 1)]
>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state__startswith='p'))]
[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)]
>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__size__gt=4))]
[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)]
>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__smelly=True))]
[(u'bar', 1), (u'foo', 2), (u'ter', 1)]
>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, min_count=2, filters=dict(perch__smelly=True))]
[(u'foo', 2)]
>>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=4))]
[(u'bar', False), (u'baz', False), (u'foo', False), (u'ter', False)]
>>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=99))]
[]

# Related tags
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=True)]
[(u'baz', 1), (u'foo', 1), (u'ter', 2)]
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, min_count=2)]
[(u'ter', 2)]
>>> [tag.name for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=False)]
[u'baz', u'foo', u'ter']
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter']), Parrot, counts=True)]
[(u'baz', 1)]
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter', 'baz']), Parrot, counts=True)]
[]

# Once again, with feeling (strings)
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model('bar', Parrot, counts=True)]
[(u'baz', 1), (u'foo', 1), (u'ter', 2)]
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model('bar', Parrot, min_count=2)]
[(u'ter', 2)]
>>> [tag.name for tag in Tag.objects.related_for_model('bar', Parrot, counts=False)]
[u'baz', u'foo', u'ter']
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(['bar', 'ter'], Parrot, counts=True)]
[(u'baz', 1)]
>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(['bar', 'ter', 'baz'], Parrot, counts=True)]
[]

# Retrieving tagged objects by Model ##########################################

>>> foo = Tag.objects.get(name='foo')
>>> bar = Tag.objects.get(name='bar')
>>> baz = Tag.objects.get(name='baz')
>>> ter = Tag.objects.get(name='ter')
>>> TaggedItem.objects.get_by_model(Parrot, foo)
[<Parrot: no more>, <Parrot: pining for the fjords>]
>>> TaggedItem.objects.get_by_model(Parrot, bar)
[<Parrot: late>, <Parrot: passed on>, <Parrot: pining for the fjords>]

# Intersections are supported
>>> TaggedItem.objects.get_by_model(Parrot, [foo, baz])
[]
>>> TaggedItem.objects.get_by_model(Parrot, [foo, bar])
[<Parrot: pining for the fjords>]
>>> TaggedItem.objects.get_by_model(Parrot, [bar, ter])
[<Parrot: late>, <Parrot: passed on>]

# You can also pass Tag QuerySets
>>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'baz']))
[]
>>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'bar']))
[<Parrot: pining for the fjords>]
>>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['bar', 'ter']))
[<Parrot: late>, <Parrot: passed on>]

# You can also pass strings and lists of strings
>>> TaggedItem.objects.get_by_model(Parrot, 'foo baz')
[]
>>> TaggedItem.objects.get_by_model(Parrot, 'foo bar')
[<Parrot: pining for the fjords>]
>>> TaggedItem.objects.get_by_model(Parrot, 'bar ter')
[<Parrot: late>, <Parrot: passed on>]
>>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'baz'])
[]
>>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'bar'])
[<Parrot: pining for the fjords>]
>>> TaggedItem.objects.get_by_model(Parrot, ['bar', 'ter'])
[<Parrot: late>, <Parrot: passed on>]

# Issue 50 - Get by non-existent tag
>>> TaggedItem.objects.get_by_model(Parrot, 'argatrons')
[]

# Unions
>>> TaggedItem.objects.get_union_by_model(Parrot, ['foo', 'ter'])
[<Parrot: late>, <Parrot: no more>, <Parrot: passed on>, <Parrot: pining for the fjords>]
>>> TaggedItem.objects.get_union_by_model(Parrot, ['bar', 'baz'])
[<Parrot: late>, <Parrot: passed on>, <Parrot: pining for the fjords>]

# Retrieving related objects by Model #########################################

# Related instances of the same Model
>>> l1 = Link.objects.create(name='link 1')
>>> Tag.objects.update_tags(l1, 'tag1 tag2 tag3 tag4 tag5')
>>> l2 = Link.objects.create(name='link 2')
>>> Tag.objects.update_tags(l2, 'tag1 tag2 tag3')
>>> l3 = Link.objects.create(name='link 3')
>>> Tag.objects.update_tags(l3, 'tag1')
>>> l4 = Link.objects.create(name='link 4')
>>> TaggedItem.objects.get_related(l1, Link)
[<Link: link 2>, <Link: link 3>]
>>> TaggedItem.objects.get_related(l1, Link, num=1)
[<Link: link 2>]
>>> TaggedItem.objects.get_related(l4, Link)
[]

# Related instance of a different Model
>>> a1 = Article.objects.create(name='article 1')
>>> Tag.objects.update_tags(a1, 'tag1 tag2 tag3 tag4')
>>> TaggedItem.objects.get_related(a1, Link)
[<Link: link 1>, <Link: link 2>, <Link: link 3>]
>>> Tag.objects.update_tags(a1, 'tag6')
>>> TaggedItem.objects.get_related(a1, Link)
[]

################
# Model Fields #
################

# TagField ####################################################################

# Ensure that automatically created forms use TagField
>>> class TestForm(forms.ModelForm):
...     class Meta:
...         model = FormTest
>>> form = TestForm()
>>> form.fields['tags'].__class__.__name__
'TagField'

# Recreating string representaions of tag lists ###############################
>>> plain = Tag.objects.create(name='plain')
>>> spaces = Tag.objects.create(name='spa ces')
>>> comma = Tag.objects.create(name='com,ma')

>>> from tagging.utils import edit_string_for_tags
>>> edit_string_for_tags([plain])
u'plain'
>>> edit_string_for_tags([plain, spaces])
u'plain, spa ces'
>>> edit_string_for_tags([plain, spaces, comma])
u'plain, spa ces, "com,ma"'
>>> edit_string_for_tags([plain, comma])
u'plain "com,ma"'
>>> edit_string_for_tags([comma, spaces])
u'"com,ma", spa ces'

###############
# Form Fields #
###############

>>> t = TagField()
>>> t.clean('foo')
u'foo'
>>> t.clean('foo bar baz')
u'foo bar baz'
>>> t.clean('foo,bar,baz')
u'foo,bar,baz'
>>> t.clean('foo, bar, baz')
u'foo, bar, baz'
>>> t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar')
u'foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar'
>>> t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar')
Traceback (most recent call last):
    ...
ValidationError: [u'Each tag may be no more than 50 characters long.']
"""
#@-node:ekr.20080212031914:tests.py
#@+node:ekr.20041118062709:define encodingIvarsDict
encodingIvarsDict = {'_hash':'encodingIvarsDict'}

encodingIvarsData = (
    ("default_at_auto_file_encoding","string","utf-8"),
    ("default_derived_file_encoding","string","utf-8"),
    ("new_leo_file_encoding","string","UTF-8"),
        # Upper case for compatibility with previous versions.
    ("tkEncoding","string",None),
        # Defaults to None so it doesn't override better defaults.
)
#@-node:ekr.20041118062709:define encodingIvarsDict
#@+node:ekr.20031218072017.1463:setEncoding (leoImport)
def setEncoding (self,p=None,atAuto=False):

    # scanDirectives checks the encoding: may return None.
    c = self.c
    if p is None: p = c.currentPosition()
    theDict = g.scanDirectives(c,p)
    encoding = theDict.get("encoding")
    if encoding and g.isValidEncoding(encoding):
        self.encoding = encoding
    elif atAuto:
        self.encoding = c.config.default_at_auto_file_encoding
    else:
        # This is not great.
        self.encoding = g.app.tkEncoding

    # g.trace(self.encoding)
#@-node:ekr.20031218072017.1463:setEncoding (leoImport)
#@+node:ekr.20031218072017.3210:createOutline (leoImport)
def createOutline (self,fileName,parent,atAuto=False,s=None,ext=None):

    c = self.c ; u = c.undoer ; s1 = s

    # New in Leo 4.4.7: honor @path directives.

    self.scanDefaultDirectory(parent) # sets .defaultDirectory.
    filename = g.os_path_join(self.default_directory,fileName)
    junk,self.fileName = g.os_path_split(fileName)
    self.methodName,self.fileType = g.os_path_splitext(self.fileName)
    self.setEncoding(p=parent,atAuto=atAuto)
    # g.trace(self.fileName,self.fileType)
    # All file types except the following just get copied to the parent node.
    if not ext: ext = self.fileType
    ext = ext.lower()
    if not s:
        << Read file into s >>

    << convert s to the proper encoding >>

    # Create the top-level headline.
    if atAuto:
        p = parent.copy()
        c.beginUpdate()
        try:
            p.setTnodeText('')
        finally:
            c.endUpdate(False)
    else:
        undoData = u.beforeInsertNode(parent)
        p = parent.insertAsLastChild()
        if self.treeType == "@file":
            p.initHeadString("@file " + fileName)
        else:
            # @root nodes don't have @root in the headline.
            p.initHeadString(fileName)
        u.afterInsertNode(p,'Import',undoData)

    self.rootLine = g.choose(self.treeType=="@file","","@root-code "+self.fileName+'\n')

    if ext in (".c", ".cpp", ".cxx"):
        self.scanCText(s,p,atAuto=atAuto)
    elif ext == '.c#':
        self.scanCSharpText(s,p,atAuto=atAuto)
    elif ext == ".el":
        self.scanElispText(s,p,atAuto=atAuto)
    elif ext == ".java":
        self.scanJavaText(s,p,atAuto=atAuto)
    elif ext == ".js":
        self.scanJavaScriptText(s,p,atAuto=atAuto)
    elif ext == ".pas":
        self.scanPascalText(s,p,atAuto=atAuto)
    elif ext in (".py", ".pyw"):
        self.scanPythonText(s,p,atAuto=atAuto)
    elif ext == ".php":
        self.scanPHPText(s,p,atAuto=atAuto)
    elif ext in ('.html','.htm','.xml'):
        self.scanXmlText(s,p,atAuto=atAuto)
    else:
        self.scanUnknownFileType(s,p,ext,atAuto=atAuto)

    p.contract()
    return p
#@+node:ekr.20031218072017.3211:<< Read file into s >>
try:
    fileName = g.os_path_normpath(fileName)
    theFile = open(fileName)
    s = theFile.read()
    theFile.close()
except IOError:
    z = g.choose(atAuto,'@auto ','')
    g.es("can not open", "%s%s" % (z,fileName),color='red')
    leoTest.fail()
    return None
#@-node:ekr.20031218072017.3211:<< Read file into s >>
#@+node:ekr.20080212092908:<< convert s to the proper encoding >>
if s and fileName.endswith('.py'):
    # Python's encoding comments override everything else.
    lines = g.splitLines(s)
    tag = '# -*- coding:' ; tag2 = '-*-'
    n1,n2 = len(tag),len(tag2)
    line1 = lines[0].strip()
    if line1.startswith(tag) and line1.endswith(tag2):
        e = line1[n1:-n2].strip()
        if e and g.isValidEncoding(e):
            # print 'found',e,'in',line1
            self.encoding = e

s = g.toUnicode(s,self.encoding)
#@-node:ekr.20080212092908:<< convert s to the proper encoding >>
#@-node:ekr.20031218072017.3210:createOutline (leoImport)
#@-node:ekr.20080210201527:Fixed import problems with django
#@+node:ekr.20080213071313:Fixed new problem with image.py
I am running Leo 4.4.6 on Ubuntu Linux (Gutsy and Hardy). Upon
application startup, error was occurring on loading "image plugin".

It turns out that the version of PIL that ships with Ubuntu (and
presumably Debian and maybe some other distributions) no longer
includes the ImageTk module. ImageTk is now located in the python-
imaging-tk package.

After installing the proper PIL, it is also necessary to change the
plugin module, "[usr/local/lib/] leo/plugins/image.py".

Near the start of the program, change the line:
"import ImageTk"
to
"from PIL import ImageTk"
#@-node:ekr.20080213071313:Fixed new problem with image.py
#@+node:ekr.20080211123036:Worked around problems with winpdb
#@+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.currentPosition()
    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 = g.os_path_abspath(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.20080211123036:Worked around problems with winpdb
#@-node:ekr.20080210205942:Fixed bugs
#@+node:ekr.20080211100823:New features
#@+node:ekr.20080211100823.1:All import commands now honor @path
#@-node:ekr.20080211100823.1:All import commands now honor @path
#@+node:ekr.20080211100700.1:Added ipython plugin
#@-node:ekr.20080211100700.1:Added ipython plugin
#@+node:ekr.20080124083629:write-outline-only command now prints a 'done' message
@nocolor

Write outline only does't say anything in the log window to indicate that it worked.

@color
#@nonl
#@+node:ekr.20031218072017.3050:writeOutlineOnly
def writeOutlineOnly (self,event=None):

    '''Write the entire outline without writing any derived files.'''

    c = self.c
    c.endEditing()
    self.write_Leo_file(self.mFileName,outlineOnlyFlag=True)
    g.es('done',color='blue')
#@-node:ekr.20031218072017.3050:writeOutlineOnly
#@+node:ekr.20031218072017.3046:write_Leo_file
def write_Leo_file(self,fileName,outlineOnlyFlag,toString=False,toOPML=False):

    c = self.c
    self.putCount = 0
    self.toString = toString
    theActualFile = None
    toZip = False
    atOk = True

    if not outlineOnlyFlag or toOPML:
        # Update .leoRecentFiles.txt if possible.
        g.app.config.writeRecentFilesFile(c)
        << write all @file nodes >>
    << return if the .leo file is read-only >>
    try:
        << create backup file >>
        self.mFileName = fileName
        if toOPML:
            << ensure that filename ends with .opml >>
        self.outputFile = cStringIO.StringIO()
        << create theActualFile >>
        # t1 = time.clock()
        if toOPML:
            self.putToOPML()
        else:
            # An important optimization: we have already assign the file indices.
            self.putLeoFile()
        # t2 = time.clock()
        s = self.outputFile.getvalue()
        # g.trace(self.leo_file_encoding)
        if toZip:
            self.writeZipFile(s)
        elif toString:
            # For support of chapters plugin.
            g.app.write_Leo_file_string = s
        else:
            theActualFile.write(s)
            theActualFile.close()
            << delete backup file >>
            # t3 = time.clock()
            # g.es_print('len',len(s),'putCount',self.putCount) # 'put',t2-t1,'write&close',t3-t2)
        self.outputFile = None
        self.toString = False
        return atOk
    except Exception:
        g.es("exception writing:",fileName)
        g.es_exception(full=True)
        if theActualFile: theActualFile.close()
        self.outputFile = None
        if backupName:
            << delete fileName >>
            << rename backupName to fileName >>
        self.toString = False
        return False

write_LEO_file = write_Leo_file # For compatibility with old plugins.
#@+node:ekr.20040324080359:<< write all @file nodes >>
try:
    # Write all @file nodes and set orphan bits.
    # An important optimization: we have already assign the file indices.
    changedFiles,atOk = c.atFileCommands.writeAll()
except Exception:
    g.es_error("exception writing derived files")
    g.es_exception()
    return False
#@-node:ekr.20040324080359:<< write all @file nodes >>
#@+node:ekr.20040324080359.1:<< return if the .leo file is read-only >>
# self.read_only is not valid for Save As and Save To commands.

if g.os_path_exists(fileName):
    try:
        if not os.access(fileName,os.W_OK):
            g.es("can not write: read only:",fileName,color="red")
            return False
    except Exception:
        pass # os.access() may not exist on all platforms.
#@-node:ekr.20040324080359.1:<< return if the .leo file is read-only >>
#@+node:ekr.20031218072017.3047:<< create backup file >>
backupName = None

# rename fileName to fileName.bak if fileName exists.
if not toString and g.os_path_exists(fileName):
    backupName = g.os_path_join(g.app.loadDir,fileName)
    backupName = fileName + ".bak"
    if g.os_path_exists(backupName):
        g.utils_remove(backupName)
    ok = g.utils_rename(c,fileName,backupName)
    if not ok:
        if self.read_only:
            g.es("read only",color="red")
        return False
#@nonl
#@-node:ekr.20031218072017.3047:<< create backup file >>
#@+node:ekr.20060919070145:<< ensure that filename ends with .opml >>
if not self.mFileName.endswith('opml'):
    self.mFileName = self.mFileName + '.opml'
fileName = self.mFileName
#@nonl
#@-node:ekr.20060919070145:<< ensure that filename ends with .opml >>
#@+node:ekr.20060929103258:<< create theActualFile >>
if toString:
    theActualFile = None
elif c.isZipped:
    self.toString = toString = True
    theActualFile = None
    toZip = True
else:
    theActualFile = open(fileName, 'wb')
#@-node:ekr.20060929103258:<< create theActualFile >>
#@+node:ekr.20031218072017.3048:<< delete backup file >>
if backupName and g.os_path_exists(backupName):

    self.deleteFileWithMessage(backupName,'backup')
#@-node:ekr.20031218072017.3048:<< delete backup file >>
#@+node:ekr.20050405103712:<< delete fileName >>
if fileName and g.os_path_exists(fileName):

    self.deleteFileWithMessage(fileName,'')
#@-node:ekr.20050405103712:<< delete fileName >>
#@+node:ekr.20050405103712.1:<< rename backupName to fileName >>
if backupName:
    g.es("restoring",fileName,"from",backupName)
    g.utils_rename(c,backupName,fileName)
#@-node:ekr.20050405103712.1:<< rename backupName to fileName >>
#@+node:ekr.20070412095520:writeZipFile
def writeZipFile (self,s):

    # The name of the file in the archive.
    contentsName = g.toEncodedString(
        g.shortFileName(self.mFileName),
        self.leo_file_encoding,reportErrors=True)

    # The name of the archive itself.
    fileName = g.toEncodedString(
        self.mFileName,
        self.leo_file_encoding,reportErrors=True)

    # Write the archive.
    theFile = zipfile.ZipFile(fileName,'w',zipfile.ZIP_DEFLATED)
    theFile.writestr(contentsName,s)
    theFile.close()
#@-node:ekr.20070412095520:writeZipFile
#@-node:ekr.20031218072017.3046:write_Leo_file
#@-node:ekr.20080124083629:write-outline-only command now prints a 'done' message
#@+node:ekr.20080211180518:Installed open_with patch
@nocolor

As things are in flux vcs wise I'll use the handy `edream` vcs system
for this patch ;-)

If you're using the xemacs plugin it's nice not to have to save the Leo
outline to get changes made in emacs to propagate to the file system.
I.e. as well as ctrl-x-s in emacs you have to switch to the leo window
and ctrl-s there to actually write the file.  This patch optionally
removes that second step.

Cheers -Terry

Index: open_with.py
===================================================================
RCS file: /cvs/leo/plugins/open_with.py,v
retrieving revision 1.8
diff -r1.8 open_with.py
8a9,11
>
> Set the @setting open_with_save_on_update True to save the outline
> (including @file type nodes) whenever an update is received.
10d12
< #@nonl
133a136,137
>                         if c.config.getBool('open_with_save_on_update'):
>                             c.save()
136d139
<                     #@nonl
#@nonl
#@-node:ekr.20080211180518:Installed open_with patch
#@-node:ekr.20080211100823:New features
#@-node:ekr.20080214083121:beta 1
#@+node:ekr.20080214083121.1:beta 2
#@+node:ekr.20080214083121.2:Bug fixes
#@+node:ekr.20080214081701:old colorizer now colorizes leo directives
# The problem was due to removing '@' character from entries in g.globalDirectiveList.
#@+node:ekr.20060829084924:<< configure fonts >> (revise,maybe)
# Get the default body font.
defaultBodyfont = self.fonts.get('default_body_font')
if not defaultBodyfont:
    defaultBodyfont = c.config.getFontFromParams(
        "body_text_font_family", "body_text_font_size",
        "body_text_font_slant",  "body_text_font_weight",
        c.config.defaultBodyFontSize)
    self.fonts['default_body_font'] = defaultBodyfont

# Configure fonts.
w = c.frame.body.bodyCtrl
keys = default_font_dict.keys() ; keys.sort()
for key in keys:
    option_name = default_font_dict[key]
    # First, look for the language-specific setting, then the general setting.
    for name in ('%s_%s' % (self.language,option_name),(option_name)):
        font = self.fonts.get(name)
        if font:
            # g.trace('found',name,id(font))
            w.tag_config(key,font=font)
            break
        else:
            family = c.config.get(name + '_family','family')
            size   = c.config.get(name + '_size',  'size')   
            slant  = c.config.get(name + '_slant', 'slant')
            weight = c.config.get(name + '_weight','weight')
            if family or slant or weight or size:
                family = family or g.app.config.defaultFontFamily
                size   = size or str(c.config.defaultBodyFontSize)
                slant  = slant or 'roman'
                weight = weight or 'normal'
                font = c.config.getFontFromParams(family,size,slant,weight)
                # Save a reference to the font so it 'sticks'.
                self.fonts[name] = font 
                # g.trace(key,name,family,size,slant,weight,id(font))
                w.tag_config(key,font=font)
                break
    else: # Neither the general setting nor the language-specific setting exists.
        if len(self.fonts.keys()) > 1: # Restore the default font.
            # g.trace('default',key)
            w.tag_config(key,font=defaultBodyfont)
#@nonl
#@-node:ekr.20060829084924:<< configure fonts >> (revise,maybe)
#@+node:ekr.20031218072017.1620:doAtKeyword: NOT for cweb keywords
# Handles non-cweb keyword.

def doAtKeyword (self,s,i):

    j = self.skip_id(s,i+1,chars="-") # to handle @root-code, @root-doc
    word = s[i:j]
    word = word.lower()
    # g.trace(word,word[1:] in g.globalDirectiveList)
    if i != 0 and word not in ("@others","@all"):
        word = "" # can't be a Leo keyword, even if it looks like it.

    # 7/8/02: don't color doc parts in plain text.
    if self.language != "plain" and (word == "@" or word == "@doc"):
        # at-space is a Leo keyword.
        self.tag("leoKeyword",i,j)
        k = len(s) # Everything on the line is in the doc part.
        if not g.doHook("color-optional-markup",
            colorer=self,p=self.p,v=self.p,s=s,i=j,j=k,colortag="docPart"):
            self.tag("docPart",j,k)
        return k,"doc"
    elif word == "@nocolor":
        # Nothing on the line is colored.
        self.tag("leoKeyword",i,j)
        return j,"nocolor"
    elif word[1:] in g.globalDirectiveList:
        self.tag("leoKeyword",i,j)
        return j,"normal"
    else:
        return j,"normal"
#@-node:ekr.20031218072017.1620:doAtKeyword: NOT for cweb keywords
#@-node:ekr.20080214081701:old colorizer now colorizes leo directives
#@-node:ekr.20080214083121.2:Bug fixes
#@-node:ekr.20080214083121.1:beta 2
#@-node:ekr.20080211100700:4.4.7
#@+node:ekr.20080227135412:4.4.8
#@+node:ekr.20080324105006.1:Previous
#@+node:None.None:Bug fixes
#@+node:ekr.20080308140240:Assigned g.fileIndex on the fly
@nocolor

Eureka! I have just discovered an elegant solution to this problem!

The Aha is this:  we can create a "base" timestamp just *once*, when
the commander (i.e. the nodeIndices class) is created.  Thereafter,
code that writes gnx's simply calls nodeIndices.getIndex if the node
doesn't already have an index (or if indices are being reassigned in a
copied tree).

In other words, there is no need to be picky about timestamps, the
base timestamp will do, so there is no need to assign indices before
writing nodes!

I didn't see this before because computing timestamps is quite time
consuming.  In fact, we can't do so "on the fly".  That being so, the
old code computed the "new" timestamp once in assignFileIndices, which
lead me away from getting gnx's as needed.  But if there is only *one*
timestamp everything becomes easy.  There is no need to "batch" the
computation of gnx's.  In other words, timestamps don't have to be up-
to-the-second accurate.  They simply guarantee that two nodes (in a
particular outline) created by the *same* person can't collide.

This is the way it is written in "The Book". All calls to
assignFileIndices disappear!  All the code I wrote this morning that
remembers whether indices have been assigned disappears.  I'll keep a
do-nothing assignFileIndices around for compatibility with old
scripts.

This is *so* important.  It eliminates forever a source of serious
file write problems.  Furthermore, nodeIndices.getIndex becomes
substantially simpler. Even though the code was short, it implied some
very tricky assumptions.  Every time I read it I had to recreate why
it worked.  Those mental gymnastics should disappear.

A great day for Leo. 

@color
#@nonl
#@+node:ekr.20080308075453:From leoNodes
#@+node:ekr.20031218072017.1995:getNewIndex
def getNewIndex (self):

    '''Create a new gnx.'''

    self.lastIndex += 1
    d = (self.userId,self.timeString,self.lastIndex)
    # g.trace(d)
    return d
#@-node:ekr.20031218072017.1995:getNewIndex
#@+node:ekr.20031218072017.1998:setTimeStamp
def setTimestamp (self):

    """Set the timestamp string to be used by getNewIndex until further notice"""

    self.timeString = time.strftime(
        "%Y%m%d%H%M%S", # Help comparisons; avoid y2k problems.
        time.localtime())

    # g.trace(self.timeString,self.lastIndex,g.callers(4))

setTimeStamp = setTimestamp
#@-node:ekr.20031218072017.1998:setTimeStamp
#@+node:ekr.20031218072017.1999:toString
def toString (self,index):

    """Convert a gnx (a tuple) to its string representation"""

    try:
        theId,t,n = index
        if n in (None,0,'',):
            return "%s.%s" % (theId,t)
        else:
            return "%s.%s.%d" % (theId,t,n)
    except Exception:
        if not g.app.unitTesting:
            g.trace('unusual gnx',repr(index),g.callers()) 
        try:
            theId,t,n = self.getNewIndex()
            if n in (None,0,'',):
                return "%s.%s" % (theId,t)
            else:
                return "%s.%s.%d" % (theId,t,n)
        except Exception:
            g.trace('double exception: returning original index')
            return repr(index)
#@nonl
#@-node:ekr.20031218072017.1999:toString
#@-node:ekr.20080308075453:From leoNodes
#@+node:ekr.20080308140437:From leoAtFile
#@+node:ekr.20041005105605.188:nodeSentinelText 4.x
def nodeSentinelText(self,p):

    """Return the text of a @+node or @-node sentinel for p."""

    at = self ; h = p.headString()
    << remove comment delims from h if necessary >>

    if at.thinFile:
        if not p.v.t.fileIndex:
            p.v.t.fileIndex = g.app.nodeIndices.getNewIndex()
        gnx = g.app.nodeIndices.toString(p.v.t.fileIndex)
        return "%s:%s" % (gnx,h)
    else:
        return h
#@+node:ekr.20041005105605.189:<< remove comment delims from h if necessary >>
@ Bug fix 1/24/03:

If the present @language/@comment settings do not specify a single-line comment we remove all block comment delims from h.  This prevents headline text from interfering with the parsing of node sentinels.
@c

start = at.startSentinelComment
end = at.endSentinelComment

if end and len(end) > 0:
    h = h.replace(start,"")
    h = h.replace(end,"")
#@-node:ekr.20041005105605.189:<< remove comment delims from h if necessary >>
#@-node:ekr.20041005105605.188:nodeSentinelText 4.x
#@+node:ekr.20041005105605.72:createThinChild4
def createThinChild4 (self,gnxString,headline):

    """Find or create a new *vnode* whose parent (also a vnode) is at.lastThinNode."""

    at = self ; c = at.c ; indices = g.app.nodeIndices
    last = at.lastThinNode ; lastIndex = last.t.fileIndex
    gnx = indices.scanGnx(gnxString,0)

    # New in Leo 4.4a5: Solve Read @file nodes problem (by LeoUser)
    if self._forcedGnxPositionList and last in self._forcedGnxPositionList:
        last.fileIndex = lastIndex =  gnx
        self._forcedGnxPositionList.remove(last)

    if 0:
        g.trace("last",last,last.t.fileIndex)
        g.trace("args",indices.areEqual(gnx,last.t.fileIndex),gnxString,headline)

    # See if there is already a child with the proper index.
    child = at.lastThinNode.firstChild()
    while child and not indices.areEqual(gnx,child.t.fileIndex):
        child = child.next()

    if at.cloneSibCount > 1:
        n = at.cloneSibCount ; at.cloneSibCount = 0
        if child: clonedSibs,junk = at.scanForClonedSibs(child)
        else: clonedSibs = 0
        copies = n - clonedSibs
        # g.trace(copies,headline)
    else:
        if indices.areEqual(gnx,lastIndex):
            last.t.setVisited() # Supress warning/deletion of unvisited nodes.
            return last
        if child:
            child.t.setVisited() # Supress warning/deletion of unvisited nodes.
            return child
        copies = 1 # Create exactly one copy.

    while copies > 0:
        copies -= 1
        # Create the tnode only if it does not already exist.
        tnodesDict = c.fileCommands.tnodesDict
        t = tnodesDict.get(gnxString)
        if t:
            if indices.areEqual(t.fileIndex,gnx):
                pass
            else:
                g.trace('can not happen: t.fileIndex: %s gnx: %s' % (t.fileIndex,gnx))
                # g.trace('not created, should already exist',gnxString)
        else:
            t = leoNodes.tnode(bodyString=None,headString=headline)
            t.fileIndex = gnx
            tnodesDict[gnxString] = t
        parent = at.lastThinNode
        child = leoNodes.vnode(t)
        t.vnodeList.append(child)
        child.linkAsNthChild(parent,parent.numberOfChildren())
        # g.trace('creating last child %s\nof parent%s\n' % (child,parent))

    child.t.setVisited() # Supress warning/deletion of unvisited nodes.
    return child
#@-node:ekr.20041005105605.72:createThinChild4
#@-node:ekr.20080308140437:From leoAtFile
#@+node:ekr.20080308140902:From leoFileCommands
#@+node:ekr.20031218072017.1570:assignFileIndices & compactFileIndices
def assignFileIndices (self):

    """Assign a file index to all tnodes"""

    pass # No longer needed: we assign indices as needed.

# Indices are now immutable, so there is no longer any difference between these two routines.
compactFileIndices = assignFileIndices
#@-node:ekr.20031218072017.1570:assignFileIndices & compactFileIndices
#@+node:ekr.20031218072017.1557:finishPaste
def finishPaste(self,reassignIndices=True):

    """Finish pasting an outline from the clipboard.

    Retain clone links if reassignIndices is False."""

    c = self.c
    current = c.currentPosition()
    if reassignIndices:
        << reassign tnode indices >>
    c.selectPosition(current)
    return current
#@+node:ekr.20031218072017.1558:<< reassign tnode indices >>
# We must *reassign* indices here so no "False clones" are created.

nodeIndices = g.app.nodeIndices

current.clearVisitedInTree()

for p in current.self_and_subtree_iter():
    t = p.v.t
    if not t.isVisited():
        t.setVisited()
        t.fileIndex = nodeIndices.getNewIndex()
#@-node:ekr.20031218072017.1558:<< reassign tnode indices >>
#@-node:ekr.20031218072017.1557:finishPaste
#@+node:ekr.20031218072017.1577:putTnode
def putTnode (self,t):

    # New in Leo 4.4.8.  Assign v.t.fileIndex here as needed.
    if not t.fileIndex:
        g.trace('can not happen: no index for tnode',t)
        t.fileIndex = g.app.nodeIndices.getNewIndex()

    # New in Leo 4.4.2 b2: call put just once.
    gnx = g.app.nodeIndices.toString(t.fileIndex)
    ua = hasattr(t,'unknownAttributes') and self.putUnknownAttributes(t) or ''
    body = t.bodyString and xml.sax.saxutils.escape(t.bodyString) or ''
    self.put('<t tx="%s"%s>%s</t>\n' % (gnx,ua,body))
#@-node:ekr.20031218072017.1577:putTnode
#@+node:ekr.20031218072017.1575:putTnodes
def putTnodes (self):

    """Puts all tnodes as required for copy or save commands"""

    c = self.c

    self.put("<tnodes>\n")
    << write only those tnodes that were referenced >>
    self.put("</tnodes>\n")
#@+node:ekr.20031218072017.1576:<< write only those tnodes that were referenced >>
if self.usingClipboard: # write the current tree.
    theIter = c.currentPosition().self_and_subtree_iter()
else: # write everything
    theIter = c.allNodes_iter()

# Populate tnodes
tnodes = {}
nodeIndices = g.app.nodeIndices
for p in theIter:
    # New in Leo 4.4.8: assign file indices here.
    if not p.v.t.fileIndex:
        p.v.t.fileIndex = nodeIndices.getNewIndex()
    tnodes[p.v.t.fileIndex] = p.v.t

# Put all tnodes in index order.
keys = tnodes.keys() ; keys.sort()
for index in keys:
    # g.trace(index)
    t = tnodes.get(index)
    if not t:
        g.trace('can not happen: no tnode for',index)
    # Write only those tnodes whose vnodes were written.
    if t.isWriteBit():
        self.putTnode(t)
#@nonl
#@-node:ekr.20031218072017.1576:<< write only those tnodes that were referenced >>
#@-node:ekr.20031218072017.1575:putTnodes
#@+node:ekr.20031218072017.1579:putVnodes & helpers
def putVnodes (self):

    """Puts all <v> elements in the order in which they appear in the outline."""

    c = self.c
    c.clearAllVisited()

    self.put("<vnodes>\n")

    # Make only one copy for all calls.
    self.currentPosition = c.currentPosition() 
    self.rootPosition    = c.rootPosition()
    self.topPosition     = c.topPosition()

    if self.usingClipboard:
        self.putVnode(self.currentPosition) # Write only current tree.
    else:
        for p in c.rootPosition().self_and_siblings_iter():
            # New in Leo 4.4.2 b2 An optimization:
            self.putVnode(p,isIgnore=p.isAtIgnoreNode()) # Write the next top-level node.

    self.put("</vnodes>\n")
#@nonl
#@+node:ekr.20031218072017.1863:putVnode (3.x and 4.x)
def putVnode (self,p,isIgnore=False):

    """Write a <v> element corresponding to a vnode."""

    fc = self ; c = fc.c ; v = p.v
    # Not writing @auto nodes is way too dangerous.
    isAuto = p.isAtAutoNode() and p.atAutoNodeName().strip()
    isThin = p.isAtThinFileNode()
    isOrphan = p.isOrphan()
    if not isIgnore: isIgnore = p.isAtIgnoreNode()

    # forceWrite = isIgnore or not isThin or (isThin and isOrphan)
    if isIgnore: forceWrite = True      # Always write full @ignore trees.
    elif isAuto: forceWrite = False     # Never write non-ignored @auto trees.
    elif isThin: forceWrite = isOrphan  # Only write orphan @thin trees.
    else:        forceWrite = True      # Write all other @file trees.

    << Set gnx = tnode index >>
    attrs = []
    << Append attribute bits to attrs >>
    << Append tnodeList and unKnownAttributes to attrs >>
    attrs = ''.join(attrs)
    v_head = '<v t="%s"%s><vh>%s</vh>' % (gnx,attrs,xml.sax.saxutils.escape(p.v.headString()or''))
    # The string catentation is faster than repeated calls to fc.put.
    if not self.usingClipboard:
        << issue informational messages >>
    # New in 4.2: don't write child nodes of @file-thin trees (except when writing to clipboard)
    if p.hasChildren() and (forceWrite or self.usingClipboard):
        fc.put('%s\n' % v_head)
        # This optimization eliminates all "recursive" copies.
        p.moveToFirstChild()
        while 1:
            fc.putVnode(p,isIgnore)
            if p.hasNext(): p.moveToNext()
            else:           break
        p.moveToParent() # Restore p in the caller.
        fc.put('</v>\n')
    else:
        fc.put('%s</v>\n' % v_head) # Call put only once.
#@+node:ekr.20031218072017.1864:<< Set gnx = tnode index >>
# New in Leo 4.4.8.  Assign v.t.fileIndex here as needed.
if not v.t.fileIndex:
    v.t.fileIndex = g.app.nodeIndices.getNewIndex()

gnx = g.app.nodeIndices.toString(v.t.fileIndex)

if forceWrite or self.usingClipboard:
    v.t.setWriteBit() # 4.2: Indicate we wrote the body text.
#@-node:ekr.20031218072017.1864:<< Set gnx = tnode index >>
#@+node:ekr.20031218072017.1865:<< Append attribute bits to attrs >>
# These string catenations are benign because they rarely happen.
attr = ""
if v.isExpanded(): attr += "E"
if v.isMarked():   attr += "M"
if v.isOrphan():   attr += "O"

# No longer a bottleneck now that we use p.equal rather than p.__cmp__
# Almost 30% of the entire writing time came from here!!!
if not self.use_sax:
    if p.equal(self.topPosition):     attr += "T" # was a bottleneck
    if p.equal(self.currentPosition): attr += "V" # was a bottleneck

if attr:
    attrs.append(' a="%s"' % attr)

# Put the archived *current* position in the *root* positions <v> element.
if self.use_sax and p.equal(self.rootPosition):
    aList = [str(z) for z in self.currentPosition.archivedPosition()]
    d = hasattr(v,'unKnownAttributes') and v.unknownAttributes or {}
    d['str_leo_pos'] = ','.join(aList)
    # g.trace(aList,d)
    v.unknownAttributes = d
#@nonl
#@-node:ekr.20031218072017.1865:<< Append attribute bits to attrs >>
#@+node:ekr.20040324082713:<< Append tnodeList and unKnownAttributes to attrs>>
# Write the tnodeList only for @file nodes.
# New in 4.2: tnode list is in tnode.

# Debugging.
# if v.isAnyAtFileNode():
    # if hasattr(v.t,"tnodeList"):
        # g.trace(v.headString(),len(v.t.tnodeList))
    # else:
        # g.trace(v.headString(),"no tnodeList")

if hasattr(v.t,"tnodeList") and len(v.t.tnodeList) > 0 and v.isAnyAtFileNode():
    if isThin:
        if g.app.unitTesting:
            g.app.unitTestDict["warning"] = True
        g.es("deleting tnode list for",p.headString(),color="blue")
        # This is safe: cloning can't change the type of this node!
        delattr(v.t,"tnodeList")
    else:
        attrs.append(fc.putTnodeList(v)) # New in 4.0

if hasattr(v,"unknownAttributes"): # New in 4.0
    attrs.append(self.putUnknownAttributes(v))

if p.hasChildren() and not forceWrite and not self.usingClipboard:
    # We put the entire tree when using the clipboard, so no need for this.
    attrs.append(self.putDescendentUnknownAttributes(p))
    attrs.append(self.putDescendentAttributes(p))
#@nonl
#@-node:ekr.20040324082713:<< Append tnodeList and unKnownAttributes to attrs>>
#@+node:ekr.20040702085529:<< issue informational messages >>
if isOrphan and isThin:
    g.es("writing erroneous:",p.headString(),color="blue")
    p.clearOrphan()
#@-node:ekr.20040702085529:<< issue informational messages >>
#@-node:ekr.20031218072017.1863:putVnode (3.x and 4.x)
#@+node:ekr.20031218072017.2002:putTnodeList (4.0,4.2)
def putTnodeList (self,v):

    """Put the tnodeList attribute of a tnode."""

    # Remember: entries in the tnodeList correspond to @+node sentinels, _not_ to tnodes!
    nodeIndices = g.app.nodeIndices
    tnodeList = v.t.tnodeList

    if tnodeList:
        # g.trace("%4d" % len(tnodeList),v)
        for t in tnodeList:
            try: # Will fail for None or any pre 4.1 file index.
                junk,junk,junk = t.fileIndex
            except Exception:
                gnx = nodeIndices.getNewIndex()
                # Apparent bug fix: Leo 4.4.8, 2008-3-8: use t, not v.t here!
                # t.setFileIndex(gnx) # Don't convert to string until the actual write.
                t.fileIndex = gnx
        s = ','.join([nodeIndices.toString(t.fileIndex) for t in tnodeList])
        return ' tnodeList="%s"' % (s)
    else:
        return ''
#@nonl
#@-node:ekr.20031218072017.2002:putTnodeList (4.0,4.2)
#@+node:ekr.20040701065235.2:putDescendentAttributes
def putDescendentAttributes (self,p):

    nodeIndices = g.app.nodeIndices

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

    result = []
    for theList,tag in ((marks,"marks"),(expanded,"expanded")):
        if theList:
            sList = []
            for t in theList:
                # New in Leo 4.4.8.  Assign t.fileIndex here as needed.
                if not t.fileIndex:
                    t.fileIndex = g.app.nodeIndices.getNewIndex()
                gnx = t.fileIndex
                sList.append("%s," % nodeIndices.toString(gnx))
            s = string.join(sList,'')
            # g.trace(tag,[str(p.headString()) for p in theList])
            result.append('\n%s="%s"' % (tag,s))

    return ''.join(result)
#@-node:ekr.20040701065235.2:putDescendentAttributes
#@+node:EKR.20040627113418:putDescendentUnknownAttributes
def putDescendentUnknownAttributes (self,p):

    # pychecker complains about dumps.

    # The bin param doesn't exist in Python 2.3;
    # the protocol param doesn't exist in earlier versions of Python.
    version = '.'.join([str(sys.version_info[i]) for i in (0,1)])
    python23 = g.CheckVersion(version,'2.3')

    # Create a list of all tnodes having a valid unknownAttributes dict.
    tnodes = []
    tnodesData = []
    for p2 in p.subtree_iter():
        t = p2.v.t
        if hasattr(t,"unknownAttributes"):
            if t not in tnodes :
                # g.trace(p2.headString(),t)
                tnodes.append(t) # Bug fix: 10/4/06.
                tnodesData.append((p2,t),)

    # Create a list of pairs (t,d) where d contains only pickleable entries.
    data = []
    for p,t in tnodesData:
        if type(t.unknownAttributes) != type({}):
            g.es("ignoring non-dictionary unknownAttributes for",p,color="blue")
        else:
            # Create a new dict containing only entries that can be pickled.
            d = dict(t.unknownAttributes) # Copy the dict.

            for key in d.keys():
                try:
                    # We don't actually save the pickled values here.
                    if python23:
                        pickle.dumps(d[key],protocol=1) # Requires Python 2.3
                    else:
                        pickle.dumps(d[key],bin=True) # Requires earlier versions of Python.
                except pickle.PicklingError:
                    del d[key]
                    g.es("ignoring bad unknownAttributes key",key,"in",p,color="blue")
                except Exception:
                    del d[key]
                    g.es('putDescendentUnknownAttributes: unexpected pickling exception',color='red')
                    g.es_exception()
            data.append((t,d),)

    # Create resultDict, an enclosing dict to hold all the data.
    resultDict = {}
    nodeIndices = g.app.nodeIndices
    for t,d in data:
        # New in Leo 4.4.8.  Assign v.t.fileIndex here as needed.
        if not t.fileIndex:
            t.fileIndex = g.app.nodeIndices.getNewIndex()
        gnx = nodeIndices.toString(t.fileIndex)
        resultDict[gnx]=d

    if 0:
        print "resultDict..."
        for key in resultDict:
            print repr(key),repr(resultDict.get(key))

    # Pickle and hexlify resultDict.
    if resultDict:
        try:
            tag = "descendentTnodeUnknownAttributes"
            if python23:
                s = pickle.dumps(resultDict,protocol=1) # Requires Python 2.3
                # g.trace('protocol=1')
            else:
                s = pickle.dumps(resultDict,bin=True) # Requires Earlier version of Python.
                # g.trace('bin=True')
            field = ' %s="%s"' % (tag,binascii.hexlify(s))
            return field
        except pickle.PicklingError:
            g.trace("putDescendentUnknownAttributes can't happen 1",color="red")
        except Exception:
            g.es("putDescendentUnknownAttributes can't happen 2",color='red')
            g.es_exception()
    return ''
#@-node:EKR.20040627113418:putDescendentUnknownAttributes
#@-node:ekr.20031218072017.1579:putVnodes & helpers
#@-node:ekr.20080308140902:From leoFileCommands
#@-node:ekr.20080308140240:Assigned g.fileIndex on the fly
#@+node:ekr.20080308151956.3:Allow tkFrame to complete before killing it
#@+node:ekr.20031218072017.1974:destroySelf (tkFrame)
def destroySelf (self):

    # g.trace(self)

    # Remember these: we are about to destroy all of our ivars!
    top = self.top 
    c = self.c

    # Indicate that the commander is no longer valid.
    c.exists = False

    # New in Leo 4.4.8: Finish all window tasks before killing the window.
    top.update()

    # g.trace(self)

    # Important: this destroys all the objects of the commander too.
    self.destroyAllObjects()

    # New in Leo 4.4.8: Finish all window tasks before killing the window.
    top.update()

    c.exists = False # Make sure this one ivar has not been destroyed.

    top.destroy()
#@-node:ekr.20031218072017.1974:destroySelf (tkFrame)
#@+node:ekr.20040803072955.58:tree.redraw_now & helper
# New in 4.4b2: suppress scrolling by default.

def redraw_now (self,scroll=False):

    '''Redraw immediately: used by Find so a redraw doesn't mess up selections in headlines.'''

    if g.app.quitting or self.drag_p or self.frame not in g.app.windowList:
        return

    c = self.c

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

    if not g.app.unitTesting:
        if self.gc_before_redraw:
            g.collectGarbage()
        if g.app.trace_gc_verbose:
            if (self.redrawCount % 5) == 0:
                g.printGcSummary()
        if self.trace_redraw or self.trace_alloc:
            # g.trace(self.redrawCount,g.callers())
            # g.trace(c.rootPosition().headString(),'canvas:',id(self.canvas),g.callers())
            if self.trace_stats:
                g.print_stats()
                g.clear_stats()

    # New in 4.4b2: Call endEditLabel, but suppress the redraw.
    self.beginUpdate()
    try:
        self.endEditLabel()
    finally:
        self.endUpdate(False)

    # Do the actual redraw.
    self.expandAllAncestors(c.currentPosition())
    if self.idle_redraw:
        def idleRedrawCallback(event=None,self=self,scroll=scroll):
            self.redrawHelper(scroll=scroll)
        self.canvas.after_idle(idleRedrawCallback)
    else:
        self.redrawHelper(scroll=scroll)
    if g.app.unitTesting:
        self.canvas.update_idletasks() # Important for unit tests.
    c.masterFocusHandler()

redraw = redraw_now # Compatibility
#@+node:ekr.20040803072955.59:redrawHelper
def redrawHelper (self,scroll=True):

    # This can be called at idle time, so there are shutdown issues.
    if g.app.quitting or self.drag_p or self.frame not in g.app.windowList:
        return
    if not hasattr(self,'c'):
        return

    c = self.c ; trace = False
    oldcursor = self.canvas['cursor']
    self.canvas['cursor'] = "watch"

    if not g.doHook("redraw-entire-outline",c=c):

        if trace: g.trace('scroll',scroll,g.callers())
        c.setTopVnode(None)
        self.setVisibleAreaToFullCanvas()
        self.drawTopTree()
        # Set up the scroll region after the tree has been redrawn.
        bbox = self.canvas.bbox('all')
        # g.trace('canvas',self.canvas,'bbox',bbox)
        if bbox is None:
            x0,y0,x1,y1 = 0,0,100,100
        else:
            x0, y0, x1, y1 = bbox
        self.canvas.configure(scrollregion=(0, 0, x1, y1))
        if scroll:
            self.canvas.update_idletasks() # Essential.
            self.scrollTo()

    g.doHook("after-redraw-outline",c=c)

    self.canvas['cursor'] = oldcursor
#@-node:ekr.20040803072955.59:redrawHelper
#@-node:ekr.20040803072955.58:tree.redraw_now & helper
#@+node:ekr.20040803072955.65:scrollTo
def scrollTo(self,p=None):

    """Scrolls the canvas so that p is in view."""

    # This can be called at idle time, so there are shutdown issues.
    if g.app.quitting or self.drag_p or self.frame not in g.app.windowList:
        return
    if not hasattr(self,'c'):
        return

    # __pychecker__ = '--no-argsused' # event not used.
    # __pychecker__ = '--no-intdivide' # suppress warning about integer division.

    c = self.c ; frame = c.frame ; trace = False
    if not p or not c.positionExists(p):
        p = c.currentPosition()
    if not p or not c.positionExists(p):
        if trace: g.trace('current p does not exist',p)
        p = c.rootPosition()
    if not p or not c.positionExists(p):
        if trace: g.trace('no position')
        return
    try:
        h1 = self.yoffset(p)
        if self.center_selected_tree_node: # New in Leo 4.4.3.
            << compute frac0 >>
            delta = abs(self.prevMoveToFrac-frac0)
            # g.trace(delta)
            if delta > 0.0:
                self.prevMoveToFrac = frac0
                self.canvas.yview("moveto",frac0)
                if trace: g.trace("frac0 %1.2f %3d %3d %3d" % (frac0,h1,htot,wtot))
        else:
            last = c.lastVisible()
            nextToLast = last.visBack(c)
            h2 = self.yoffset(last)
            << compute approximate line height >>
            << Compute the fractions to scroll down/up >>
            if frac <= lo: # frac is for scrolling down.
                if self.prevMoveToFrac != frac:
                    self.prevMoveToFrac = frac
                    self.canvas.yview("moveto",frac)
                    if trace: g.trace("frac  %1.2f %3d %3d %1.2f %1.2f" % (frac, h1,h2,lo,hi))
            elif frac2 + (hi - lo) >= hi: # frac2 is for scrolling up.
                if self.prevMoveToFrac != frac2:
                    self.prevMoveToFrac = frac2
                    self.canvas.yview("moveto",frac2)
                    if trace: g.trace("frac2 %1.2f %3d %3d %1.2f %1.2f" % (frac2,h1,h2,lo,hi))

        if self.allocateOnlyVisibleNodes:
            self.canvas.after_idle(self.idle_second_redraw)

        c.setTopVnode(p) # 1/30/04: remember a pseudo "top" node.

    except:
        g.es_exception()

idle_scrollTo = scrollTo # For compatibility.
#@+node:ekr.20061030091926:<< compute frac0 >>
# frac0 attempt to put the 
scrollRegion = self.canvas.cget('scrollregion')
geom = self.canvas.winfo_geometry()

if scrollRegion and geom:
    scrollRegion = scrollRegion.split(' ')
    # g.trace('scrollRegion',repr(scrollRegion))
    htot = int(scrollRegion[3])
    wh,junk,junk = geom.split('+')
    junk,h = wh.split('x')
    if h: wtot = int(h)
    else: wtot = 500
    # g.trace('geom',geom,'wtot',wtot)
    if htot > 0.1:
        frac0 = float(h1-wtot/2)/float(htot)
        frac0 = max(min(frac0,1.0),0.0)
    else:
        frac0 = 0.0
else:
    frac0 = 0.0 ; htot = wtot = 0
#@-node:ekr.20061030091926:<< compute frac0 >>
#@+node:ekr.20040803072955.66:<< compute approximate line height >>
if nextToLast: # 2/2/03: compute approximate line height.
    lineHeight = h2 - self.yoffset(nextToLast)
else:
    lineHeight = 20 # A reasonable default.
#@-node:ekr.20040803072955.66:<< compute approximate line height >>
#@+node:ekr.20040803072955.67:<< Compute the fractions to scroll down/up >>
data = frame.canvas.leo_treeBar.get() # Get the previous values of the scrollbar.
try: lo, hi = data
except: lo,hi = 0.0,1.0

# h1 and h2 are the y offsets of the present and last nodes.
if h2 > 0.1:
    frac = float(h1)/float(h2) # For scrolling down.
    frac2 = float(h1+lineHeight/2)/float(h2) # For scrolling up.
    frac2 = frac2 - (hi - lo)
else:
    frac = frac2 = 0.0 # probably any value would work here.

frac =  max(min(frac,1.0),0.0)
frac2 = max(min(frac2,1.0),0.0)
#@nonl
#@-node:ekr.20040803072955.67:<< Compute the fractions to scroll down/up >>
#@-node:ekr.20040803072955.65:scrollTo
#@+node:ekr.20051104075904.42:runLeoTest
def runLeoTest(c,path,verbose=False,full=False):

    frame = None ; ok = False ; old_gui = g.app.gui

    # Do not set or clear g.app.unitTesting: that is only done in leoTest.runTest.

    assert g.app.unitTesting

    try:
        ok, frame = g.openWithFileName(path,c,enableLog=False)
        assert(ok and frame)
        errors = frame.c.checkOutline(verbose=verbose,unittest=True,full=full)
        assert(errors == 0)
        ok = True
    finally:
        g.app.gui = old_gui
        if frame and frame.c != c:
            frame.c.setChanged(False)
            g.app.closeLeoWindow(frame)
        c.frame.update() # Restored in Leo 4.4.8.
#@-node:ekr.20051104075904.42:runLeoTest
#@+node:ekr.20051104075904.99:createUnitTestsFromDoctests
def createUnitTestsFromDoctests (modules,verbose=True):

    created = False # True if suite is non-empty.

    suite = unittest.makeSuite(unittest.TestCase)

    for module in list(modules):
        # New in Python 4.2: n may be zero.
        try:
            test = doctest.DocTestSuite(module)
            n = test.countTestCases()
            if n > 0:
                suite.addTest(test)
                created = True
                if verbose:
                    print "found %2d doctests for %s" % (n,module.__name__)
        except ValueError:
            pass # No tests found.

    return g.choose(created,suite,None)
#@-node:ekr.20051104075904.99:createUnitTestsFromDoctests
#@-node:ekr.20080308151956.3:Allow tkFrame to complete before killing it
#@+node:ekr.20080308151956.2:Renamed self.fileIndex to  readBufferIndex in file read logic
# This should have been done long ago.
#@nonl
#@+node:ekr.20031218072017.3020:Reading
#@+node:ekr.20060919104836: Top-level
#@+node:ekr.20070919133659.1:checkLeoFile (fileCommands)
def checkLeoFile (self,event=None):

    fc = self ; c = fc.c ; p = c.currentPosition()

    # Put the body (minus the @nocolor) into the file buffer.
    s = p.bodyString() ; tag = '@nocolor\n'
    if s.startswith(tag): s = s[len(tag):]
    self.fileBuffer = s ; self.fileBufferIndex = 0

    # Do a trial read.
    self.checking = True
    self.initReadIvars()
    c.loading = True # disable c.changed
    try:
        try:
            self.getAllLeoElements(fileName='check-leo-file',silent=False)
            g.es_print('check-leo-file passed',color='blue')
        except BadLeoFile, message:
            # g.es_exception()
            g.es_print('check-leo-file failed:',str(message),color='red')
    finally:
        self.checking = False
        c.loading = False # reenable c.changed
#@-node:ekr.20070919133659.1:checkLeoFile (fileCommands)
#@+node:ekr.20031218072017.1559:getLeoOutlineFromClipboard & helpers
def getLeoOutlineFromClipboard (self,s,reassignIndices=True):

    '''Read a Leo outline from string s in clipboard format.'''

    try:
        v = self.getLeoOutlineHelper(s,reassignIndices,checking=True)
        v = self.getLeoOutlineHelper(s,reassignIndices,checking=False)
    except invalidPaste:
        v = None
        g.es("invalid Paste As Clone",color="blue")
    except BadLeoFile:
        v = None
        g.es("the clipboard is not valid ",color="blue")

    return v

getLeoOutline = getLeoOutlineFromClipboard # for compatibility
#@nonl
#@+node:ekr.20031218072017.1557:finishPaste
def finishPaste(self,reassignIndices=True):

    """Finish pasting an outline from the clipboard.

    Retain clone links if reassignIndices is False."""

    c = self.c
    current = c.currentPosition()
    if reassignIndices:
        << reassign tnode indices >>
    c.selectPosition(current)
    return current
#@+node:ekr.20031218072017.1558:<< reassign tnode indices >>
# We must *reassign* indices here so no "False clones" are created.

nodeIndices = g.app.nodeIndices

current.clearVisitedInTree()

for p in current.self_and_subtree_iter():
    t = p.v.t
    if not t.isVisited():
        t.setVisited()
        t.fileIndex = nodeIndices.getNewIndex()
#@-node:ekr.20031218072017.1558:<< reassign tnode indices >>
#@-node:ekr.20031218072017.1557:finishPaste
#@+node:ekr.20060826052453.1:getLeoOutlineHelper
def getLeoOutlineHelper (self,s,reassignIndices,checking):

    self.checking = checking
    self.usingClipboard = True
    self.fileBuffer = s ; self.fileBufferIndex = 0
    self.descendentUnknownAttributesDictList = []
    v = None

    self.tnodesDict = {}
    if not reassignIndices:
        << recreate tnodesDict >>
    try:
        self.getXmlVersionTag()
        self.getXmlStylesheetTag()
        self.getTag("<leo_file>")
        self.getClipboardHeader()
        self.getDummyElements()
        self.getVnodes(reassignIndices)
        self.getTnodes()
        self.getTag("</leo_file>")
        if not checking:
            v = self.finishPaste(reassignIndices)
    finally:
        self.fileBuffer = None ; self.fileBufferIndex = 0
        self.usingClipboard = False
        self.tnodesDict = {}
    return v
#@+node:EKR.20040610134756:<< recreate tnodesDict >>
nodeIndices = g.app.nodeIndices

self.tnodesDict = {}

for t in self.c.all_unique_tnodes_iter():
    # Bug fix: Leo 4.4.8: all tnodes have a fileIndex field: make sure it is non-None.
    if hasattr(t,'fileIndex') and t.fileIndex:
        tref = t.fileIndex
        if nodeIndices.isGnx(tref):
            tref = nodeIndices.toString(tref)
        self.tnodesDict[tref] = t

if 0:
    print '-'*40
    for key in self.tnodesDict.keys():
        print key,self.tnodesDict[key]
#@-node:EKR.20040610134756:<< recreate tnodesDict >>
#@-node:ekr.20060826052453.1:getLeoOutlineHelper
#@+node:ekr.20031218072017.3022:getClipboardHeader
def getClipboardHeader (self):

    if self.getOpenTag("<leo_header"):
        return # <leo_header> or <leo_header/> has been seen.

    while 1:
        if self.matchTag("file_format="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("tnodes="):
            self.getDquote() ; self.getLong() ; self.getDquote() # no longer used
        elif self.matchTag("max_tnode_index="):
            self.getDquote() ; self.getLong() ; self.getDquote() # no longer used
        elif self.matchTag("></leo_header>"): # new in 4.2: allow this form.
            break
        else:
            self.getTag("/>")
            break
#@nonl
#@-node:ekr.20031218072017.3022:getClipboardHeader
#@-node:ekr.20031218072017.1559:getLeoOutlineFromClipboard & helpers
#@+node:ekr.20031218072017.1553:getLeoFile
# The caller should enclose this in begin/endUpdate.

def getLeoFile (self,theFile,fileName,readAtFileNodesFlag=True,silent=False):

    c = self.c
    c.setChanged(False) # May be set when reading @file nodes.
    << warn on read-only files >>
    self.checking = False
    self.mFileName = c.mFileName
    self.initReadIvars()
    c.loading = True # disable c.changed

    try:
        ok = True
        # t1 = time.clock()
        if self.use_sax:
            v = self.readSaxFile(theFile,fileName,silent)
            if v: # v == None for minimal .leo files.
                c.setRootVnode(v)
                self.rootVnode = v
            else:
                self.rootVnode = c.rootPosition().v
        else:
            self.getAllLeoElements(fileName,silent)
        # t2 = time.clock()
        # g.trace('time',t2-t1)
    except BadLeoFile, message:
        if not silent:
            g.es_exception()
            g.alert(self.mFileName + " is not a valid Leo file: " + str(message))
        ok = False

    # New in Leo 4.2.2: before reading derived files.
    if self.use_sax:
        self.resolveTnodeLists()
    if ok and readAtFileNodesFlag:
        # Redraw before reading the @file nodes so the screen isn't blank.
        # This is important for big files like LeoPy.leo.
        c.redraw_now()
        c.atFileCommands.readAll(c.rootVnode(),partialFlag=False)

    # Do this after reading derived files.
    if readAtFileNodesFlag:
        # The descendent nodes won't exist unless we have read the @thin nodes!
        self.restoreDescendentAttributes()
    if self.use_sax:
        self.setPositionsFromVnodes()
    else:
        if not self.usingClipboard:
            self.setPositionsFromStacks()
        if not c.currentPosition():
            c.setCurrentPosition(c.rootPosition())

    c.selectVnode(c.currentPosition()) # load body pane
    c.loading = False # reenable c.changed
    c.setChanged(c.changed) # Refresh the changed marker.
    self.initReadIvars()
    return ok, self.ratio
#@nonl
#@+node:ekr.20031218072017.1554:<< warn on read-only files >>
# os.access may not exist on all platforms.

try:
    self.read_only = not os.access(fileName,os.W_OK)
except AttributeError:
    self.read_only = False
except UnicodeError:
    self.read_only = False

if self.read_only:
    g.es("read only:",fileName,color="red")
#@-node:ekr.20031218072017.1554:<< warn on read-only files >>
#@-node:ekr.20031218072017.1553:getLeoFile
#@+node:ekr.20031218072017.2009:newTnode
def newTnode(self,index):

    if self.tnodesDict.has_key(index):
        g.es("bad tnode index:",str(index),"using empty text.")
        return leoNodes.tnode()
    else:
        # Create the tnode.  Use the _original_ index as the key in tnodesDict.
        t = leoNodes.tnode()
        self.tnodesDict[index] = t

        if type(index) not in (type(""),type(u"")):
            g.es("newTnode: unexpected index type:",type(index),index,color="red")

        # Convert any pre-4.1 index to a gnx.
        junk,theTime,junk = gnx = g.app.nodeIndices.scanGnx(index,0)
        if theTime != None:
            t.fileIndex = gnx

        return t
#@-node:ekr.20031218072017.2009:newTnode
#@+node:ekr.20031218072017.3029:readAtFileNodes (fileCommands)
def readAtFileNodes (self):

    c = self.c ; p = c.currentPosition()

    c.beginUpdate()
    try:
        c.atFileCommands.readAll(p,partialFlag=True)
    finally:
        c.endUpdate()

    # Force an update of the body pane.
    c.setBodyString(p,p.bodyString())
    c.frame.body.onBodyChanged(undoType=None)
#@-node:ekr.20031218072017.3029:readAtFileNodes (fileCommands)
#@+node:ekr.20031218072017.2297:open (leoFileCommands)
def open(self,theFile,fileName,readAtFileNodesFlag=True,silent=False):

    c = self.c ; frame = c.frame
    if not self.use_sax:
        << read the entire file into the buffer >>
        theFile.close()
        self.fileBufferIndex = 0
    << Set the default directory >>
    self.topPosition = None
    ok, ratio = self.getLeoFile(
        theFile,fileName,
        readAtFileNodesFlag=readAtFileNodesFlag,
        silent=silent)
    frame.resizePanesToRatio(ratio,frame.secondary_ratio)
    if 0: # 1/30/04: this is useless.
        if self.topPosition: 
            c.setTopVnode(self.topPosition)
    if not self.use_sax: # Delete the file buffer
        self.fileBuffer = ""
    return ok
#@nonl
#@+node:ekr.20070412103240:<< read the entire file into the buffer >>
isZipped = zipfile.is_zipfile(fileName)

if isZipped:
    aList = theFile.infolist()
    contentsName = aList[0].filename
    self.fileBuffer = theFile.read(contentsName)
else:
    self.fileBuffer = theFile.read()
#@-node:ekr.20070412103240:<< read the entire file into the buffer >>
#@+node:ekr.20031218072017.2298:<< Set the default directory >>
@ The most natural default directory is the directory containing the .leo file that we are about to open.  If the user has specified the "Default Directory" preference that will over-ride what we are about to set.
@c

theDir = g.os_path_dirname(fileName)

if len(theDir) > 0:
    c.openDirectory = theDir
#@-node:ekr.20031218072017.2298:<< Set the default directory >>
#@-node:ekr.20031218072017.2297:open (leoFileCommands)
#@+node:ekr.20031218072017.3030:readOutlineOnly
def readOutlineOnly (self,theFile,fileName):

    c = self.c
    # Read the entire file into the buffer
    self.fileBuffer = theFile.read() ; theFile.close()
    self.fileBufferIndex = 0
    << Set the default directory >>
    c.beginUpdate()
    try:
        ok, ratio = self.getLeoFile(theFile,fileName,readAtFileNodesFlag=False)
    finally:
        c.endUpdate()
    c.frame.deiconify()
    junk,junk,secondary_ratio = self.frame.initialRatios()
    c.frame.resizePanesToRatio(ratio,secondary_ratio)
    if 0: # 1/30/04: this is useless.
        # This should be done after the pane size has been set.
        if self.topPosition:
            c.frame.tree.setTopPosition(self.topPosition)
            c.redraw_now()
    # delete the file buffer
    self.fileBuffer = ""
    return ok
#@+node:ekr.20071211134300:<< Set the default directory >>
@ The most natural default directory is the directory containing the .leo file that we are about to open.  If the user has specified the "Default Directory" preference that will over-ride what we are about to set.
@c

theDir = g.os_path_dirname(fileName)

if len(theDir) > 0:
    c.openDirectory = theDir
#@-node:ekr.20071211134300:<< Set the default directory >>
#@-node:ekr.20031218072017.3030:readOutlineOnly
#@-node:ekr.20060919104836: Top-level
#@+node:ekr.20060919133249:Common
# Methods common to both the sax and non-sax code.
#@nonl
#@+node:ekr.20031218072017.2004:canonicalTnodeIndex
def canonicalTnodeIndex(self,index):

    """Convert Tnnn to nnn, leaving gnx's unchanged."""

    # index might be Tnnn, nnn, or gnx.
    junk,theTime,junk = g.app.nodeIndices.scanGnx(index,0)
    if theTime == None: # A pre-4.1 file index.
        if index[0] == "T":
            index = index[1:]

    return index
#@-node:ekr.20031218072017.2004:canonicalTnodeIndex
#@+node:ekr.20040701065235.1:getDescendentAttributes
def getDescendentAttributes (self,s,tag=""):

    '''s is a list of gnx's, separated by commas from a <v> or <t> element.
    Parses s into a list.

    This is used to record marked and expanded nodes.
    '''

    # __pychecker__ = '--no-argsused' # tag used only for debugging.

    gnxs = s.split(',')
    result = [gnx for gnx in gnxs if len(gnx) > 0]
    # g.trace(tag,result)
    return result
#@-node:ekr.20040701065235.1:getDescendentAttributes
#@+node:EKR.20040627114602:getDescendentUnknownAttributes
# Only @thin vnodes have the descendentTnodeUnknownAttributes field.
# The question is: what are we to do about this?

def getDescendentUnknownAttributes (self,s):

    try:
        bin = binascii.unhexlify(s) # Throws a TypeError if val is not a hex string.
        val = pickle.loads(bin)
        return val

    except (TypeError,pickle.UnpicklingError,ImportError):
        g.trace('Can not unpickle',s)
        return None
#@-node:EKR.20040627114602:getDescendentUnknownAttributes
#@+node:ekr.20060919142200.1:initReadIvars
def initReadIvars (self):

    self.descendentUnknownAttributesDictList = []
    self.descendentExpandedList = []
    self.descendentMarksList = []
    self.tnodesDict = {}
#@nonl
#@-node:ekr.20060919142200.1:initReadIvars
#@+node:EKR.20040627120120:restoreDescendentAttributes
def restoreDescendentAttributes (self):

    c = self.c ; verbose = True 

    for resultDict in self.descendentUnknownAttributesDictList:
        for gnx in resultDict.keys():
            tref = self.canonicalTnodeIndex(gnx)
            t = self.tnodesDict.get(tref)
            if t:
                t.unknownAttributes = resultDict[gnx]
                t._p_changed = 1
            elif verbose:
                g.trace('can not find tnode (duA): gnx = %s' % gnx,color='red')
    marks = {} ; expanded = {}
    for gnx in self.descendentExpandedList:
        tref = self.canonicalTnodeIndex(gnx)
        t = self.tnodesDict.get(gnx)
        if t: expanded[t]=t
        elif verbose:
            g.trace('can not find tnode (expanded): gnx = %s, tref: %s' % (gnx,tref),color='red')

    for gnx in self.descendentMarksList:
        tref = self.canonicalTnodeIndex(gnx)
        t = self.tnodesDict.get(gnx)
        if t: marks[t]=t
        elif verbose:
            g.trace('can not find tnode (marks): gnx = %s tref: %s' % (gnx,tref),color='red')

    if marks or expanded:
        # g.trace('marks',len(marks),'expanded',len(expanded))
        for p in c.all_positions_iter():
            if marks.get(p.v.t):
                p.v.initMarkedBit()
                    # This was the problem: was p.setMark.
                    # There was a big performance bug in the mark hook in the Node Navigator plugin.
            if expanded.get(p.v.t):
                p.expand()
#@-node:EKR.20040627120120:restoreDescendentAttributes
#@-node:ekr.20060919133249:Common
#@+node:ekr.20031218072017.3021:Non-sax
#@+node:ekr.20040326052245:convertStackToPosition
def convertStackToPosition (self,stack):

    c = self.c ; p2 = None
    if not stack: return None

    for p in c.allNodes_iter():
        if p.v == stack[0]:
            p2 = p.copy()
            for n in xrange(len(stack)):
                if not p2: break
                # g.trace("compare",n,p2.v,stack[n])
                if p2.v != stack[n]:
                    p2 = None
                elif n + 1 == len(stack):
                    break
                else:
                    p2.moveToParent()
            if p2:
                return p

    return None
#@-node:ekr.20040326052245:convertStackToPosition
#@+node:ekr.20031218072017.1243:get, match & skip (basic)
#@+node:ekr.20031218072017.1244:get routines (basic)
#@+node:EKR.20040526204706:getBool
def getBool (self):

    self.skipWs() # guarantees at least one more character.
    ch = self.fileBuffer[self.fileBufferIndex]
    if ch == '0':
        self.fileBufferIndex += 1 ; return False
    elif ch == '1':
        self.fileBufferIndex += 1 ; return True
    else:
        raise BadLeoFile("expecting bool constant")
#@-node:EKR.20040526204706:getBool
#@+node:EKR.20040526204706.1:getDouble
def getDouble (self):

    self.skipWs()
    i = self.fileBufferIndex ; buf = self.fileBuffer
    floatChars = 'eE.+-'
    n = len(buf)
    while i < n and (buf[i].isdigit() or buf[i] in floatChars):
        i += 1
    if i == self.fileBufferIndex:
        raise BadLeoFile("expecting float constant")
    val = float(buf[self.fileBufferIndex:i])
    self.fileBufferIndex = i
    return val
#@-node:EKR.20040526204706.1:getDouble
#@+node:EKR.20040526204706.2:getDqBool
def getDqBool (self):

    self.getDquote()
    val = self.getBool()
    self.getDquote()
    return val
#@-node:EKR.20040526204706.2:getDqBool
#@+node:EKR.20040526204706.3:getDqString
def getDqString (self):

    self.getDquote()
    i = self.fileBufferIndex
    self.fileBufferIndex = j = string.find(self.fileBuffer,'"',i)
    if j == -1: raise BadLeoFile("unterminated double quoted string")
    s = self.fileBuffer[i:j]
    self.getDquote()
    return s
#@-node:EKR.20040526204706.3:getDqString
#@+node:EKR.20040526204706.4:getDquote
def getDquote (self):

    self.getTag('"')
#@-node:EKR.20040526204706.4:getDquote
#@+node:ekr.20031218072017.3024:getEscapedString
def getEscapedString (self):

    # The next '<' begins the ending tag.
    i = self.fileBufferIndex
    self.fileBufferIndex = j = string.find(self.fileBuffer,'<',i)
    if j == -1:
        print self.fileBuffer[i:]
        raise BadLeoFile("unterminated escaped string")
    else:
        # Allocates memory
        return self.xmlUnescape(self.fileBuffer[i:j])
#@-node:ekr.20031218072017.3024:getEscapedString
#@+node:EKR.20040526204706.5:getIndex
def getIndex (self):

    val = self.getLong()
    if val < 0: raise BadLeoFile("expecting index")
    return val
#@-node:EKR.20040526204706.5:getIndex
#@+node:EKR.20040526204706.6:getLong
def getLong (self):

    self.skipWs() # guarantees at least one more character.
    i = self.fileBufferIndex
    if self.fileBuffer[i] == u'-':
        i += 1
    n = len(self.fileBuffer)
    while i < n and self.fileBuffer[i].isdigit():
        i += 1
    if i == self.fileBufferIndex:
        raise BadLeoFile("expecting int constant")
    val = int(self.fileBuffer[self.fileBufferIndex:i])
    self.fileBufferIndex = i
    return val
#@-node:EKR.20040526204706.6:getLong
#@+node:EKR.20040526204706.7:getOpenTag
def getOpenTag (self,tag):

    """
    Look ahead for collapsed tag: tag may or may not end in ">"
    Skips tag and /> if found, otherwise does not alter index.
    Returns True if the closing part was found.
    Throws BadLeoFile if the tag does not exist.
    """

    if tag[-1] == ">":
        # Only the tag itself or a collapsed tag are valid.
        if self.matchTag(tag):
            return False # Not a collapsed tag.
        elif self.matchTag(tag[:-1]):
            # It must be a collapsed tag.
            self.skipWs()
            if self.matchTag("/>"):
                return True
        print "getOpenTag(", tag, ") failed:"
        raise BadLeoFile("expecting" + tag)
    else:
        # The tag need not be followed by "/>"
        if self.matchTag(tag):
            old_index = self.fileBufferIndex
            self.skipWs()
            if self.matchTag("/>"):
                return True
            else:
                self.fileBufferIndex = old_index
                return False
        else:
            print "getOpenTag(", tag, ") failed:"
            raise BadLeoFile("expecting" + tag)
#@-node:EKR.20040526204706.7:getOpenTag
#@+node:EKR.20040526204706.8:getStringToTag
def getStringToTag (self,tag):

    buf = self.fileBuffer
    blen = len(buf) ; tlen = len(tag)
    i = j = self.fileBufferIndex
    while i < blen:
        if tag == buf[i:i+tlen]:
            self.fileBufferIndex = i
            return buf[j:i]
        else: i += 1

    raise BadLeoFile("expecting string terminated by " + tag)
#@-node:EKR.20040526204706.8:getStringToTag
#@+node:EKR.20040526204706.9:getTag
def getTag (self,tag):

    """
    Look ahead for closing />
    Return True if found.
    """

    if self.matchTag(tag):
        return
    else:
        print "getTag(", tag, ") failed:"
        raise BadLeoFile("expecting" + tag)
#@-node:EKR.20040526204706.9:getTag
#@+node:EKR.20040526204036:getUnknownTag
def getUnknownTag(self):

    self.skipWsAndNl() # guarantees at least one more character.
    tag = self.getStringToTag('=')
    if not tag:
        print "getUnknownTag failed"
        raise BadLeoFile("unknown tag not followed by '='")

    self.fileBufferIndex += 1
    val = self.getDqString()
    # g.trace(tag,val)
    return tag,val
#@-node:EKR.20040526204036:getUnknownTag
#@-node:ekr.20031218072017.1244:get routines (basic)
#@+node:ekr.20031218072017.1245:match routines
def matchChar (self,ch):
    self.skipWs() # guarantees at least one more character.
    if ch == self.fileBuffer[self.fileBufferIndex]:
        self.fileBufferIndex += 1 ; return True
    else: return False

# Warning: does not check for end-of-word,
# so caller must match prefixes first.
def matchTag (self,tag):
    self.skipWsAndNl() # guarantees at least one more character.
    i = self.fileBufferIndex
    if tag == self.fileBuffer[i:i+len(tag)]:
        self.fileBufferIndex += len(tag)
        return True
    else:
        return False

def matchTagWordIgnoringCase (self,tag):
    self.skipWsAndNl() # guarantees at least one more character.
    i = self.fileBufferIndex
    tag = string.lower(tag)
    j = g.skip_c_id(self.fileBuffer,i)
    word = self.fileBuffer[i:j]
    word = string.lower(word)
    if tag == word:
        self.fileBufferIndex += len(tag)
        return True
    else:
        return False
#@-node:ekr.20031218072017.1245:match routines
#@+node:ekr.20031218072017.3027:skipWs
def skipWs (self):

    while self.fileBufferIndex < len(self.fileBuffer):
        ch = self.fileBuffer[self.fileBufferIndex]
        if ch == ' ' or ch == '\t':
            self.fileBufferIndex += 1
        else: break

    # The caller is entitled to get the next character.
    if  self.fileBufferIndex >= len(self.fileBuffer):
        raise BadLeoFile("")
#@-node:ekr.20031218072017.3027:skipWs
#@+node:ekr.20031218072017.3028:skipWsAndNl
def skipWsAndNl (self):

    while self.fileBufferIndex < len(self.fileBuffer):
        ch = self.fileBuffer[self.fileBufferIndex]
        if ch == ' ' or ch == '\t' or ch == '\r' or ch == '\n':
            self.fileBufferIndex += 1
        else: break

    # The caller is entitled to get the next character.
    if  self.fileBufferIndex >= len(self.fileBuffer):
        raise BadLeoFile("")
#@-node:ekr.20031218072017.3028:skipWsAndNl
#@+node:ekr.20031218072017.3031:xmlUnescape
def xmlUnescape(self,s):

    if s:
        s = string.replace(s, '\r', '')
        s = string.replace(s, "&lt;", '<')
        s = string.replace(s, "&gt;", '>')
        s = string.replace(s, "&amp;", '&')
    return s
#@-node:ekr.20031218072017.3031:xmlUnescape
#@-node:ekr.20031218072017.1243:get, match & skip (basic)
#@+node:ekr.20031218072017.1555:getAllLeoElements
def getAllLeoElements (self,fileName,silent):
    c = self.c

    self.getXmlVersionTag()
    self.getXmlStylesheetTag()

    self.getTag("<leo_file>") # Must match exactly.
    self.getLeoHeader()
    self.getGlobals()
    self.getPrefs()
    self.getFindPanelSettings()

    # Causes window to appear.
    c.frame.resizePanesToRatio(c.frame.ratio,c.frame.secondary_ratio)
    if not silent and not g.unitTesting:
        g.es("reading:",fileName)

    self.getVnodes()
    self.getTnodes()
    self.getCloneWindows()
    self.getTag("</leo_file>")
#@nonl
#@-node:ekr.20031218072017.1555:getAllLeoElements
#@+node:ekr.20031218072017.3023:getCloneWindows
# For compatibility with old file formats.

def getCloneWindows (self):

    if not self.matchTag("<clone_windows>"):
        return # <clone_windows/> seen.

    while self.matchTag("<clone_window vtag=\"V"):
        self.getLong() ; self.getDquote() ; self.getTag(">")
        if not self.getOpenTag("<global_window_position"):
            self.getTag("<global_window_position")
            self.getPosition()
            self.getTag("/>")
        self.getTag("</clone_window>")
    self.getTag("</clone_windows>")
#@-node:ekr.20031218072017.3023:getCloneWindows
#@+node:ekr.20061209141653:getDummyElements
def getDummyElements (self):

    # New in Leo 4.4.3: Ignore the dummy elements that allow
    # Pasted Leo outlines to be valid .leo files.
    while 1:
        for tag in ('<globals','<preferences','<find_panel_settings'):
            if self.matchTag(tag) and self.matchTag('/>'):
                break
        else:
            break
#@-node:ekr.20061209141653:getDummyElements
#@+node:ekr.20031218072017.2064:getFindPanelSettings
def getFindPanelSettings (self):

    if self.getOpenTag("<find_panel_settings"):
        return # <find_panel_settings/> seen.

    # New in 4.3: ignore all pre-4.3 find settings.
    while 1:
        if   self.matchTag("batch="):           self.getDqBool()
        elif self.matchTag("ignore_case="):     self.getDqBool()
        elif self.matchTag("mark_changes="):    self.getDqBool()
        elif self.matchTag("mark_finds="):      self.getDqBool()
        elif self.matchTag("node_only="):       self.getDqBool()
        elif self.matchTag("pattern_match="):   self.getDqBool()
        elif self.matchTag("reverse="):         self.getDqBool()
        elif self.matchTag("script_change="):   self.getDqBool()
        elif self.matchTag("script_search="):   self.getDqBool()
        elif self.matchTag("search_headline="): self.getDqBool()
        elif self.matchTag("search_body="):     self.getDqBool()
        elif self.matchTag("selection_only="):  self.getDqBool()
        elif self.matchTag("suboutline_only="): self.getDqBool()
        elif self.matchTag("whole_word="):      self.getDqBool()
        elif self.matchTag("wrap="):            self.getDqBool()
        elif self.matchTag(">"): break
        else: self.getUnknownTag() # Ignore all other tags.
    # Allow only <find_string> or <find_string/>
    if self.getOpenTag("<find_string>"): 
        pass
    else:
        self.getEscapedString() ; self.getTag("</find_string>")
    # Allow only <change_string> or <change_string/>
    if self.getOpenTag("<change_string>"): 
        pass
    else:
        self.getEscapedString() ; self.getTag("</change_string>")
    self.getTag("</find_panel_settings>")
#@-node:ekr.20031218072017.2064:getFindPanelSettings
#@+node:ekr.20031218072017.2306:getGlobals
def getGlobals (self):

    if self.getOpenTag("<globals"):
        # <globals/> seen: set reasonable defaults:
        self.ratio = 0.5
        y,x,h,w = 50,50,500,700
    else:
        self.getTag("body_outline_ratio=\"")
        self.ratio = self.getDouble() ; self.getDquote() ; self.getTag(">")

        self.getTag("<global_window_position")
        y,x,h,w = self.getPosition()
        self.getTag("/>")

        self.getTag("<global_log_window_position")
        self.getPosition()
        self.getTag("/>") # no longer used.

        self.getTag("</globals>")

    # Redraw the window before writing into it.
    self.frame.setTopGeometry(w,h,x,y)
    self.frame.deiconify()
    self.frame.lift()
    self.frame.update()
#@-node:ekr.20031218072017.2306:getGlobals
#@+node:ekr.20031218072017.1970:getLeoHeader
def getLeoHeader (self):

    if self.getOpenTag("<leo_header"):
        return # <leo_header/> seen.

    # New in version 1.7: attributes may appear in any order.
    while 1:
        if self.matchTag("file_format="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("tnodes="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("max_tnode_index="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("clone_windows="):
            self.getDquote() ; self.getLong() ; self.getDquote() # no longer used.
        elif self.matchTag("></leo_header>"): # new in 4.2: allow this form.
            break
        else:
            self.getTag("/>")
            break
#@-node:ekr.20031218072017.1970:getLeoHeader
#@+node:ekr.20031218072017.3025:getPosition
def getPosition (self):

    top = left = height = width = 0
    # New in version 1.7: attributes may appear in any order.
    while 1:
        if self.matchTag("top=\""):
            top = self.getLong() ; self.getDquote()
        elif self.matchTag("left=\""):
            left = self.getLong() ; self.getDquote()
        elif self.matchTag("height=\""):
            height = self.getLong() ; self.getDquote()
        elif self.matchTag("width=\""):
            width = self.getLong() ; self.getDquote()
        else: break
    return top, left, height, width
#@-node:ekr.20031218072017.3025:getPosition
#@+node:ekr.20031218072017.2062:getPrefs
# Note: Leo 4.3 does not write these settings to local .leo files.
# Instead, corresponding settings are contained in leoConfig.leo files.

def getPrefs (self):

    c = self.c

    if self.getOpenTag("<preferences"):
        return # <preferences/> seen

    table = (
        ("allow_rich_text",None,None), # Ignored.
        ("tab_width","tab_width",self.getLong),
        ("page_width","page_width",self.getLong),
        ("tangle_bat","tangle_batch_flag",self.getBool),
        ("untangle_bat","untangle_batch_flag",self.getBool),
        ("output_doc_chunks","output_doc_flag",self.getBool),
        ("noweb_flag",None,None), # Ignored.
        ("extended_noweb_flag",None,None), # Ignored.
        ("defaultTargetLanguage","target_language",self.getTargetLanguage),
        ("use_header_flag","use_header_flag",self.getBool))

    done = False
    while 1:
        found = False
        for tag,var,f in table:
            if self.matchTag("%s=" % tag):
                if var:
                    self.getDquote() ; val = f() ; self.getDquote()
                    setattr(c,var,val)
                    # g.trace(var,val)
                else:
                    self.getDqString()
                found = True ; break
        if not found:
            if self.matchTag("/>"):
                done = True ; break
            if self.matchTag(">"):
                break
            else: # New in 4.1: ignore all other tags.
                self.getUnknownTag()

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

    # Must match longer tags before short prefixes.
    for name in g.app.language_delims_dict.keys():
        if self.matchTagWordIgnoringCase(name):
            language = name.replace("/","")
            # self.getDquote()
            return language

    return "c" # default
#@-node:ekr.20031218072017.2063:getTargetLanguage
#@-node:ekr.20031218072017.2062:getPrefs
#@+node:ekr.20031218072017.3026:getSize (not used!)
def getSize (self):

    # New in version 1.7: attributes may appear in any order.
    height = 0 ; width = 0
    while 1:
        if self.matchTag("height=\""):
            height = self.getLong() ; self.getDquote()
        elif self.matchTag("width=\""):
            width = self.getLong() ; self.getDquote()
        else: break
    return height, width
#@-node:ekr.20031218072017.3026:getSize (not used!)
#@+node:ekr.20031218072017.1561:getTnode
def getTnode (self):

    # we have already matched <t.
    index = -1 ; attrDict = {}

    # New in Leo 4.4: support collapsed tnodes.
    if self.matchTag('/>'): # A collapsed tnode.
        return

    # Attributes may appear in any order.
    while 1:
        if self.matchTag("tx="):
            # New for 4.1.  Read either "Tnnn" or "gnx".
            index = self.getDqString()
        elif self.matchTag("rtf=\"1\""): pass # ignored
        elif self.matchTag("rtf=\"0\""): pass # ignored
        elif self.matchTag(">"):         break
        else: # New for 4.0: allow unknown attributes.
            # New in 4.2: allow pickle'd and hexlify'ed values.
            attr,val = self.getUa("tnode")
            if attr: attrDict[attr] = val

    # index might be Tnnn, nnn, or gnx.
    junk,theTime,junk = g.app.nodeIndices.scanGnx(index,0)
    if theTime == None: # A pre-4.1 file index.
        if index[0] == "T":
            index = index[1:]

    index = self.canonicalTnodeIndex(index)
    t = self.tnodesDict.get(index)
    if t:
        << handle unknown attributes >>
        s = self.getEscapedString()
        t.setTnodeText(s,encoding=self.leo_file_encoding)
    else:
        g.es("no tnode with index:",str(index),"the text will be discarded")
    self.getTag("</t>")
#@+node:ekr.20031218072017.1564:<< handle unknown attributes >>
keys = attrDict.keys()
if keys:
    t.unknownAttributes = attrDict
    t._p_changed = 1
    if 0: # For debugging.
        g.es_print("unknown attributes for tnode",color = "blue")
        for key in keys:
            g.es_print('',"%s = %s" % (key,attrDict.get(key)))
#@-node:ekr.20031218072017.1564:<< handle unknown attributes >>
#@-node:ekr.20031218072017.1561:getTnode
#@+node:ekr.20031218072017.2008:getTnodeList (4.0,4.2)
def getTnodeList (self,s):

    """Parse a list of tnode indices in string s."""

    # Remember: entries in the tnodeList correspond to @+node sentinels, _not_ to tnodes!

    fc = self

    indexList = s.split(',') # The list never ends in a comma.
    tnodeList = []
    for index in indexList:
        index = self.canonicalTnodeIndex(index)
        t = fc.tnodesDict.get(index)
        if not t:
            # Not an error: create a new tnode and put it in fc.tnodesDict.
            # g.trace("not allocated: %s" % index)
            t = self.newTnode(index)
        tnodeList.append(t)

    # if tnodeList: g.trace(len(tnodeList))
    return tnodeList
#@-node:ekr.20031218072017.2008:getTnodeList (4.0,4.2)
#@+node:ekr.20031218072017.1560:getTnodes
def getTnodes (self):

    # A slight change: we require a tnodes element.  But Leo always writes this.
    if self.getOpenTag("<tnodes>"):
        return # <tnodes/> seen.

    while self.matchTag("<t"):
        self.getTnode()

    self.getTag("</tnodes>")
#@-node:ekr.20031218072017.1560:getTnodes
#@+node:EKR.20040526204036.1:getUa (non-sax)
# changed for 4.3.

def getUa(self,unused_nodeType):

    """Parse an unknown attribute in a <v> or <t> element."""

    # New in 4.2.  The unknown tag has been pickled and hexlify'd.
    attr,val = self.getUnknownTag()
    # g.trace(attr,repr(val))
    if not attr:
        return None,None

    # New in 4.3: leave string attributes starting with 'str_' alone.
    if attr.startswith('str_') and type(val) == type(''):
        # g.trace(attr,val)
        return attr,val

    # New in 4.3: convert attributes starting with 'b64_' using the base64 conversion.
    if 0: # Not ready yet.
        if attr.startswith('b64_'):
            try: pass
            except Exception: pass

    try:
        binString = binascii.unhexlify(val) # Throws a TypeError if val is not a hex string.
    except TypeError:
        # Assume that Leo 4.1 wrote the attribute.
        # g.trace('4.1 val:',val2)
        return attr,val
    try:
        # No change needed to support protocols.
        val2 = pickle.loads(binString)
        # g.trace('v.3 val:',val2)
        return attr,val2
    except (pickle.UnpicklingError,ImportError):
        return attr,val
    except Exception:
        return attr,val # New in Leo 4.4.5.
#@-node:EKR.20040526204036.1:getUa (non-sax)
#@+node:ekr.20031218072017.1566:getVnode & helpers
# changed for 4.2 & 4.4
def getVnode (self,parent,back,skip,appendToCurrentStack,appendToTopStack):

    v = None
    setCurrent = setExpanded = setMarked = setOrphan = setTop = False
    tref = -1 ; headline = '' ; tnodeList = None ; attrDict = {}

    # we have already matched <v.

    # New in Leo 4.4: support collapsed tnodes.
    if self.matchTag('/>'): # A collapsed vnode.
        v,skip2 = self.createVnode(parent,back,tref,headline,attrDict)
        if self.checking: return None
        else: return v

    while 1:
        if self.matchTag("a=\""):
            << Handle vnode attribute bits >>
        elif self.matchTag("t="):
            # New for 4.1.  Read either "Tnnn" or "gnx".
            tref = index = self.getDqString()
            if self.usingClipboard:
                << raise invalidPaste if the tnode is in self.forbiddenTnodes >>
        elif self.matchTag("vtag=\"V"):
            self.getIndex() ; self.getDquote() # ignored
        elif self.matchTag("tnodeList="):
            s = self.getDqString()
            tnodeList = self.getTnodeList(s) # New for 4.0
        elif self.matchTag("descendentTnodeUnknownAttributes="):
            # New for 4.2, deprecated for 4.3?
            s = self.getDqString()
            theDict = self.getDescendentUnknownAttributes(s)
            if theDict:
                self.descendentUnknownAttributesDictList.append(theDict)
        elif self.matchTag("expanded="): # New in 4.2
            s = self.getDqString()
            self.descendentExpandedList.extend(self.getDescendentAttributes(s,tag="expanded"))
        elif self.matchTag("marks="): # New in 4.2.
            s = self.getDqString()
            self.descendentMarksList.extend(self.getDescendentAttributes(s,tag="marks"))
        elif self.matchTag(">"):
            break
        else: # New for 4.0: allow unknown attributes.
            # New in 4.2: allow pickle'd and hexlify'ed values.
            attr,val = self.getUa("vnode")
            if attr: attrDict[attr] = val
    # Headlines are optional.
    if self.matchTag("<vh>"):
        headline = self.getEscapedString() ; self.getTag("</vh>")
    # g.trace("skip:",skip,"parent:",parent,"back:",back,"headline:",headline)
    if skip:
        v = self.getExistingVnode(tref,headline)
        if v: # Bug fix: 4/18/05: The headline may change during paste as clone.
            v.initHeadString(headline,encoding=self.leo_file_encoding)
    if v is None:
        v,skip2 = self.createVnode(parent,back,tref,headline,attrDict)
        if not self.checking:
            skip = skip or skip2
            if tnodeList:
                v.t.tnodeList = tnodeList # New for 4.0, 4.2: now in tnode.

    if not self.checking:
        << Set the remembered status bits >>

    # Recursively create all nested nodes.
    parent = v ; back = None
    while self.matchTag("<v"):
        append1 = appendToCurrentStack and len(self.currentVnodeStack) == 0
        append2 = appendToTopStack and len(self.topVnodeStack) == 0
        back = self.getVnode(parent,back,skip,
            appendToCurrentStack=append1,appendToTopStack=append2)

    if not self.checking:
        << Append to current or top stack >>

    # End this vnode.
    self.getTag("</v>")
    return v
#@nonl
#@+node:ekr.20031218072017.1567:<< Handle vnode attribute bits  >>
# The a=" has already been seen.
while 1:
    if   self.matchChar('C'): pass # Not used: clone bits are recomputed later.
    elif self.matchChar('D'): pass # Not used.
    elif self.matchChar('E'): setExpanded = True
    elif self.matchChar('M'): setMarked = True
    elif self.matchChar('O'): setOrphan = True
    elif self.matchChar('T'): setTop = True
    elif self.matchChar('V'): setCurrent = True
    else: break

self.getDquote()
#@-node:ekr.20031218072017.1567:<< Handle vnode attribute bits  >>
#@+node:ekr.20041023110111:<< raise invalidPaste if the tnode is in self.forbiddenTnodes >>
# Bug fix in 4.3 a1: make sure we have valid paste.
junk,theTime,junk = g.app.nodeIndices.scanGnx(index,0)
if not theTime and index[0] == "T":
    index = index[1:]

index = self.canonicalTnodeIndex(index)
t = self.tnodesDict.get(index)

if t in self.forbiddenTnodes:
    # g.trace(t)
    raise invalidPaste
#@-node:ekr.20041023110111:<< raise invalidPaste if the tnode is in self.forbiddenTnodes >>
#@+node:ekr.20031218072017.1568:<< Set the remembered status bits >>
if setCurrent:
    self.currentVnodeStack = [v]

if setTop:
    self.topVnodeStack = [v]

if setExpanded:
    v.initExpandedBit()

if setMarked:
    v.initMarkedBit() # 3/25/03: Do not call setMarkedBit here!

if setOrphan:
    v.setOrphan()
#@-node:ekr.20031218072017.1568:<< Set the remembered status bits >>
#@+node:ekr.20040326055828:<< Append to current or top stack >>
if not setCurrent and len(self.currentVnodeStack) > 0 and appendToCurrentStack:
    #g.trace("append current",v)
    self.currentVnodeStack.append(v)

if not setTop and len(self.topVnodeStack) > 0 and appendToTopStack:
    #g.trace("append top",v)
    self.topVnodeStack.append(v)
#@-node:ekr.20040326055828:<< Append to current or top stack >>
#@+node:ekr.20031218072017.1860:createVnode
# (changed for 4.2) sets skip

def createVnode (self,parent,back,tref,headline,attrDict):

    # g.trace(parent,headline)
    v = None ; c = self.c
    # Shared tnodes are placed in the file even if empty.
    if tref == -1:
        t = leoNodes.tnode()
    else:
        tref = self.canonicalTnodeIndex(tref)
        t = self.tnodesDict.get(tref)
        if not t:
            t = self.newTnode(tref)

    if self.checking: return None,False

    if back: # create v after back.
        v = back.insertAfter(t)
    elif parent: # create v as the parent's first child.
        v = parent.insertAsNthChild(0,t)
    else: # create a root vnode
        v = leoNodes.vnode(t)
        v.moveToRoot(oldRoot=None)
        c.setRootVnode(v) # New in Leo 4.4.2.

    if v not in v.t.vnodeList:
        v.t.vnodeList.append(v) # New in 4.2.

    skip = len(v.t.vnodeList) > 1
    v.initHeadString(headline,encoding=self.leo_file_encoding)
    << handle unknown vnode attributes >>
    # g.trace(skip,tref,v,v.t,len(v.t.vnodeList))
    return v,skip
#@nonl
#@+node:ekr.20031218072017.1861:<< handle unknown vnode attributes >>
keys = attrDict.keys()
if keys:
    v.unknownAttributes = attrDict
    v._p_changed = 1

    if 0: # For debugging.
        g.es_print("unknown attributes for",v.headString(),color="blue")
        for key in keys:
            g.es_print('',"%s = %s" % (key,attrDict.get(key)))
#@-node:ekr.20031218072017.1861:<< handle unknown vnode attributes >>
#@-node:ekr.20031218072017.1860:createVnode
#@+node:ekr.20040326063413:getExistingVnode
def getExistingVnode (self,tref,headline):

    assert(tref > -1)
    tref = self.canonicalTnodeIndex(tref)
    t = self.tnodesDict.get(tref)
    try:
        return t.vnodeList[0]
    except (IndexError,AttributeError):
        g.es("missing vnode:",headline,color="red")
        g.es("probably an outline topology error.")
        return None
#@-node:ekr.20040326063413:getExistingVnode
#@-node:ekr.20031218072017.1566:getVnode & helpers
#@+node:ekr.20031218072017.1565:getVnodes
def getVnodes (self,reassignIndices=True):

    c = self.c

    if self.getOpenTag("<vnodes>"):
        return # <vnodes/> seen.

    self.forbiddenTnodes = []
    back = parent = None # This routine _must_ work on vnodes!
    self.currentVnodeStack = []
    self.topVnodeStack = []

    if self.usingClipboard:
        oldRoot = c.rootPosition()
        oldCurrent = c.currentPosition()
        if not reassignIndices:
            << set self.forbiddenTnodes to tnodes than must not be pasted >>

    while self.matchTag("<v"):
        append1 = not self.usingClipboard and len(self.currentVnodeStack) == 0
        append2 = not self.usingClipboard and len(self.topVnodeStack) == 0
        back = self.getVnode(parent,back,skip=False,
            appendToCurrentStack=append1,appendToTopStack=append2)

    if self.usingClipboard and not self.checking:
        # Link in the pasted nodes after the current position.
        newRoot = c.rootPosition()
        c.setRootPosition(oldRoot)
        newRoot.v.linkAfter(oldCurrent.v)
        newCurrent = oldCurrent.copy()
        newCurrent.v = newRoot.v
        c.setCurrentPosition(newCurrent)

    self.getTag("</vnodes>")
#@+node:ekr.20041023105832:<< set self.forbiddenTnodes to tnodes than must not be pasted >>
self.forbiddenTnodes = []

for p in oldCurrent.self_and_parents_iter():
    if p.v.t not in self.forbiddenTnodes:
        self.forbiddenTnodes.append(p.v.t)

# g.trace("forbiddenTnodes",self.forbiddenTnodes)
#@-node:ekr.20041023105832:<< set self.forbiddenTnodes to tnodes than must not be pasted >>
#@-node:ekr.20031218072017.1565:getVnodes
#@+node:ekr.20031218072017.1249:getXmlStylesheetTag
def getXmlStylesheetTag (self):

    """Parses the optional xml stylesheet string, and sets the corresponding config option.

    For example, given: <?xml_stylesheet s?> the config option is s."""

    c = self.c
    tag = "<?xml-stylesheet "

    if self.matchTag(tag):
        s = self.getStringToTag("?>")
        # print "reading:", tag + s + "?>"
        c.frame.stylesheet = s
        self.getTag("?>")
#@-node:ekr.20031218072017.1249:getXmlStylesheetTag
#@+node:ekr.20031218072017.1468:getXmlVersionTag
# Parses the encoding string, and sets self.leo_file_encoding.

def getXmlVersionTag (self):

    self.getTag(g.app.prolog_prefix_string)
    encoding = self.getDqString()
    self.getTag(g.app.prolog_postfix_string)

    if g.isValidEncoding(encoding):
        self.leo_file_encoding = encoding
        # g.trace('self.leo_file_encoding:',encoding, color="blue")
    else:
        g.es("invalid encoding in .leo file:",encoding,color="red")
#@-node:ekr.20031218072017.1468:getXmlVersionTag
#@+node:ekr.20040326054052:setPositionsFromStacks (silly)
def setPositionsFromStacks (self):

    c = self.c

    current = self.convertStackToPosition(self.currentVnodeStack)

    if current:
        # g.trace('using convertStackToPosition',current)
        c.setCurrentPosition(current)
    else:
        # g.trace(self.currentVnodeStack)
        c.setCurrentPosition(c.rootPosition())

    # At present this is useless: the drawing code doesn't set the top position properly.
    if 0:
        top = self.convertStackToPosition(self.topVnodeStack)
        if top:
            c.setTopPosition(top)
#@nonl
#@-node:ekr.20040326054052:setPositionsFromStacks (silly)
#@-node:ekr.20031218072017.3021:Non-sax
#@+node:ekr.20060919104530:Sax
#@+node:ekr.20060919110638.4:createSaxVnodes & helpers
def createSaxVnodes (self, dummyRoot):

    '''**Important**: this method and its helpers are low-level code
    corresponding to link/unlink methods in leoNodes.py.
    Modify this with extreme care.'''

    children = self.createSaxChildren(dummyRoot,parent_v = None)
    firstChild = children and children[0]

    return firstChild
#@nonl
#@+node:ekr.20060919110638.5:createSaxChildren
# node is a saxNodeClass object, parent_v is a vnode.

def createSaxChildren (self, node, parent_v):

    result = []

    for child in node.children:
        tnx = child.tnx
        t = self.tnodesDict.get(tnx)
        if t:
            # A clone.  Create a new clone node, but share the subtree, i.e., the tnode.
            v = self.createSaxVnode(child,parent_v,t=t)
            # g.trace('clone',id(child),child.headString,'t',v.t)
        else:
            v = self.createSaxVnodeTree(child,parent_v)
        result.append(v)

    self.linkSiblings(result)
    if parent_v: self.linkParentAndChildren(parent_v,result)
    return result
#@nonl
#@-node:ekr.20060919110638.5:createSaxChildren
#@+node:ekr.20060919110638.6:createSaxVnodeTree
def createSaxVnodeTree (self,node,parent_v):

    v = self.createSaxVnode(node,parent_v)

    self.createSaxChildren(node,v)

    return v
#@nonl
#@-node:ekr.20060919110638.6:createSaxVnodeTree
#@+node:ekr.20060919110638.7:createSaxVnode
def createSaxVnode (self,node,parent_v,t=None):

    h = node.headString
    b = node.bodyString

    if not t:
        t = leoNodes.tnode(bodyString=b,headString=h)
        if node.tnx:
            t.fileIndex = g.app.nodeIndices.scanGnx(node.tnx,0)
    v = leoNodes.vnode(t)
    v.t.vnodeList.append(v)
    v._parent = parent_v

    index = self.canonicalTnodeIndex(node.tnx)
    self.tnodesDict [index] = t

    # g.trace('tnx','%-22s' % (index),'v',id(v),'v.t',id(v.t),'body','%-4d' % (len(b)),h)

    self.handleVnodeSaxAttributes(node,v)
    self.handleTnodeSaxAttributes(node,t)

    return v
#@nonl
#@+node:ekr.20060919110638.8:handleTnodeSaxAttributes
def handleTnodeSaxAttributes (self,node,t):

    d = node.tnodeAttributes

    aDict = {}
    for key in d.keys():
        val = d.get(key)
        val2 = self.getSaxUa(key,val)
        aDict[key] = val2

    if aDict:
        # g.trace('uA',aDict)
        t.unknownAttributes = aDict
#@nonl
#@-node:ekr.20060919110638.8:handleTnodeSaxAttributes
#@+node:ekr.20061004053644:handleVnodeSaxAttributes
# The native attributes of <v> elements are a, t, vtag, tnodeList,
# marks, expanded and descendentTnodeUnknownAttributes.

def handleVnodeSaxAttributes (self,node,v):

    d = node.attributes
    s = d.get('a')
    if s:
        # g.trace('%s a=%s %s' % (id(node),s,v.headString()))
        # 'C' (clone) and 'D' bits are not used.
        if 'M' in s: v.setMarked()
        if 'E' in s: v.expand()
        if 'O' in s: v.setOrphan()
        if 'T' in s: self.topVnode = v
        if 'V' in s:
            # g.trace('setting currentVnode',v,color='red')
            self.currentVnode = v

    s = d.get('tnodeList','')
    tnodeList = s and s.split(',')
    if tnodeList:
        # This tnode list will be resolved later.
        # g.trace('found tnodeList',v.headString(),tnodeList)
        v.tempTnodeList = tnodeList

    s = d.get('descendentTnodeUnknownAttributes') # Correct: only tnode have descendent uA's.
    if s: 
        aDict = self.getDescendentUnknownAttributes(s)
        if aDict:
            # g.trace('descendentUaDictList',aDict)
            self.descendentUnknownAttributesDictList.append(aDict)

    s = d.get('expanded')
    if s:
        aList = self.getDescendentAttributes(s,tag="expanded")
        # g.trace('expanded list',len(aList))
        self.descendentExpandedList.extend(aList)

    s = d.get('marks')
    if s:
        aList = self.getDescendentAttributes(s,tag="marks")
        # g.trace('marks list',len(aList))
        self.descendentMarksList.extend(aList)

    aDict = {}
    for key in d.keys():
        if key in self.nativeVnodeAttributes:
            if 0: g.trace('****ignoring***',key,d.get(key))
        else:
            val = d.get(key)
            val2 = self.getSaxUa(key,val)
            aDict[key] = val2
            # g.trace(key,val,val2)
    if aDict:
        # g.trace('uA',aDict)
        v.unknownAttributes = aDict
#@nonl
#@-node:ekr.20061004053644:handleVnodeSaxAttributes
#@-node:ekr.20060919110638.7:createSaxVnode
#@+node:ekr.20060919110638.9:linkParentAndChildren
def linkParentAndChildren (self, parent_v, children):

    # if children: g.trace(parent_v,len(children))

    firstChild_v = children and children[0] or None

    parent_v.t._firstChild = firstChild_v

    for child in children:
        child._parent = parent_v

    v = parent_v
    if v not in v.t.vnodeList:
        v.t.vnodeList.append(v)
#@nonl
#@-node:ekr.20060919110638.9:linkParentAndChildren
#@+node:ekr.20060919110638.10:linkSiblings
def linkSiblings (self, sibs):

    '''Set the v._back and v._next links for all vnodes v in sibs.'''

    n = len(sibs)

    for i in xrange(n):
        v = sibs[i]
        v._back = (i-1 >= 0 and sibs[i-1]) or None
        v._next = (i+1 <  n and sibs[i+1]) or None
#@nonl
#@-node:ekr.20060919110638.10:linkSiblings
#@-node:ekr.20060919110638.4:createSaxVnodes & helpers
#@+node:ekr.20060919110638.2:dumpSaxTree
def dumpSaxTree (self,root,dummy):

    if not root:
        print 'dumpSaxTree: empty tree'
        return
    if not dummy:
        root.dump()
    for child in root.children:
        self.dumpSaxTree(child,dummy=False)
#@nonl
#@-node:ekr.20060919110638.2:dumpSaxTree
#@+node:ekr.20061003093021:getSaxUa
def getSaxUa(self,attr,val):

    """Parse an unknown attribute in a <v> or <t> element.
    The unknown tag has been pickled and hexlify'd.
    """

    try:
        val = str(val)
    except UnicodeError:
        g.es_print('unexpected exception converting hexlified string to string')
        g.es_exception()

    # g.trace(attr,repr(val))

    # New in 4.3: leave string attributes starting with 'str_' alone.
    if attr.startswith('str_') and type(val) == type(''):
        # g.trace(attr,val)
        return val

    # New in 4.3: convert attributes starting with 'b64_' using the base64 conversion.
    if 0: # Not ready yet.
        if attr.startswith('b64_'):
            try: pass
            except Exception: pass

    try:
        binString = binascii.unhexlify(val) # Throws a TypeError if val is not a hex string.
    except TypeError:
        # Assume that Leo 4.1 wrote the attribute.
        g.trace('can not unhexlify',val)
        return val
    try:
        # No change needed to support protocols.
        val2 = pickle.loads(binString)
        # g.trace('v.3 val:',val2)
        return val2
    except (pickle.UnpicklingError,ImportError):
        g.trace('can not unpickle',val)
        return val
#@-node:ekr.20061003093021:getSaxUa
#@+node:ekr.20060919110638.14:parse_leo_file
def parse_leo_file (self,theFile,inputFileName,silent):

    c = self.c

    try:
        # Use cStringIo to avoid a crash in sax when inputFileName has unicode characters.
        s = theFile.read()
        theFile = cStringIO.StringIO(s)
        # g.trace(repr(inputFileName))
        node = None
        parser = xml.sax.make_parser()
        parser.setFeature(xml.sax.handler.feature_external_ges,1)
            # Include external general entities, esp. xml-stylesheet lines.
        if 0: # Expat does not read external features.
            parser.setFeature(xml.sax.handler.feature_external_pes,1)
                # Include all external parameter entities
                # Hopefully the parser can figure out the encoding from the <?xml> element.
        handler = saxContentHandler(c,inputFileName,silent)
        parser.setContentHandler(handler)
        parser.parse(theFile) # expat does not support parseString
        node = handler.getRootNode()
    except xml.sax.SAXParseException:
        g.es_print('error parsing',inputFileName,color='red')
        g.es_exception()
    except Exception:
        g.es_print('unexpected exception parsing',inputFileName,color='red')
        g.es_exception()

    return node
#@nonl
#@-node:ekr.20060919110638.14:parse_leo_file
#@+node:ekr.20060919110638.3:readSaxFile
def readSaxFile (self,theFile,fileName,silent):

    # Pass one: create the intermediate nodes.
    dummyRoot = self.parse_leo_file(theFile,fileName,silent=silent)

    # self.dumpSaxTree(dummyRoot,dummy=True)

    # Pass two: create the tree of vnodes and tnodes from the intermediate nodes.
    v = dummyRoot and self.createSaxVnodes(dummyRoot)
    return v
#@nonl
#@-node:ekr.20060919110638.3:readSaxFile
#@+node:ekr.20060919110638.11:resolveTnodeLists
def resolveTnodeLists (self):

    c = self.c

    for p in c.allNodes_iter():
        if hasattr(p.v,'tempTnodeList'):
            # g.trace(p.v.headString())
            result = []
            for tnx in p.v.tempTnodeList:
                index = self.canonicalTnodeIndex(tnx)
                t = self.tnodesDict.get(index)
                if t:
                    # g.trace(tnx,t)
                    result.append(t)
                else:
                    g.trace('No tnode for %s' % tnx)
            p.v.t.tnodeList = result
            delattr(p.v,'tempTnodeList')
#@nonl
#@-node:ekr.20060919110638.11:resolveTnodeLists
#@+node:ekr.20060919110638.13:setPositionsFromVnodes & helper
def setPositionsFromVnodes (self):

    c = self.c ; p = c.rootPosition()

    current = None
    d = hasattr(p.v,'unknownAttributes') and p.v.unknownAttributes
    if d:
        s = d.get('str_leo_pos')
        if s:
            current = self.archivedPositionToPosition(s)

    c.setCurrentPosition(current or c.rootPosition())
#@nonl
#@+node:ekr.20061006104837.1:archivedPositionToPosition
def archivedPositionToPosition (self,s):

    c = self.c
    aList = s.split(',')
    try:
        aList = [int(z) for z in aList]
    except Exception:
        g.trace('oops: bad archived position:',aList)
        aList = None
    if not aList: return None
    p = c.rootPosition() ; level = 0
    while level < len(aList):
        i = aList[level]
        while i > 0:
            if p.hasNext():
                p.moveToNext()
                i -= 1
            else:
                g.trace('oops: bad archived position:',aList)
                return None
        level += 1
        if level < len(aList):
            p.moveToFirstChild()
            # g.trace('level',level,'index',aList[level],p.headString())
    return p
#@nonl
#@-node:ekr.20061006104837.1:archivedPositionToPosition
#@-node:ekr.20060919110638.13:setPositionsFromVnodes & helper
#@-node:ekr.20060919104530:Sax
#@-node:ekr.20031218072017.3020:Reading
#@-node:ekr.20080308151956.2:Renamed self.fileIndex to  readBufferIndex in file read logic
#@+node:ekr.20080310072732.1:Made sure that all fileIndex fields get converted on read
#@+node:ekr.20080310065458.3:Trace
@nocolor

reading settings in C:\leo-editor\trunk\leo\config\leoSettings.leo
reading settings in C:\Documents and Settings\HP_Administrator\My Documents\Edward\myLeoSettings.leo
reading settings in C:\leo-editor\trunk\leo\src\LeoPy.leo
reading C:\Documents and Settings\HP_Administrator\My Documents\Edward\.leoRecentFiles.txt
@enabled-plugins found in myLeoSettings.leo
rst3 plugin: SilverCity not loaded
@command: unit-test = <Alt+Key+4>
@command: ext-unit-test = <Alt+Key+6>
wrote recent file: C:\Documents and Settings\HP_Administrator\My Documents\Edward\.leoRecentFiles.txt
@enabled-plugins found in myLeoSettings.leo
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
@enabled-plugins found in myLeoSettings.leo
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
putTnodes: can not happen: no p.v.t.fileIndex for kill
putTnodes: can not happen: no p.v.t.fileIndex for kill
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
putTnodes: can not happen: no p.v.t.fileIndex for kill
putTnodes: can not happen: no p.v.t.fileIndex for kill
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
toString: unusual gnx None
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
putTnodes: can not happen: no p.v.t.fileIndex for Bug fixes
putTnodes: can not happen: no p.v.t.fileIndex for Features
>>>

@color
#@nonl
#@+node:ekr.20031218072017.1999:toString
def toString (self,index):

    """Convert a gnx (a tuple) to its string representation"""

    try:
        theId,t,n = index
        if n in (None,0,'',):
            return "%s.%s" % (theId,t)
        else:
            return "%s.%s.%d" % (theId,t,n)
    except Exception:
        if not g.app.unitTesting:
            g.trace('unusual gnx',repr(index),g.callers()) 
        try:
            theId,t,n = self.getNewIndex()
            if n in (None,0,'',):
                return "%s.%s" % (theId,t)
            else:
                return "%s.%s.%d" % (theId,t,n)
        except Exception:
            g.trace('double exception: returning original index')
            return repr(index)
#@nonl
#@-node:ekr.20031218072017.1999:toString
#@+node:ekr.20031218072017.1575:putTnodes
def putTnodes (self):

    """Puts all tnodes as required for copy or save commands"""

    c = self.c

    self.put("<tnodes>\n")
    << write only those tnodes that were referenced >>
    self.put("</tnodes>\n")
#@+node:ekr.20031218072017.1576:<< write only those tnodes that were referenced >>
if self.usingClipboard: # write the current tree.
    theIter = c.currentPosition().self_and_subtree_iter()
else: # write everything
    theIter = c.allNodes_iter()

# Populate tnodes
tnodes = {}
nodeIndices = g.app.nodeIndices
for p in theIter:
    # New in Leo 4.4.8: assign file indices here.
    if not p.v.t.fileIndex:
        p.v.t.fileIndex = nodeIndices.getNewIndex()
    tnodes[p.v.t.fileIndex] = p.v.t

# Put all tnodes in index order.
keys = tnodes.keys() ; keys.sort()
for index in keys:
    # g.trace(index)
    t = tnodes.get(index)
    if not t:
        g.trace('can not happen: no tnode for',index)
    # Write only those tnodes whose vnodes were written.
    if t.isWriteBit():
        self.putTnode(t)
#@nonl
#@-node:ekr.20031218072017.1576:<< write only those tnodes that were referenced >>
#@-node:ekr.20031218072017.1575:putTnodes
#@-node:ekr.20080310065458.3:Trace
#@+node:ekr.20080310093038.6:To be studied
#@+node:ekr.20060826052453.1:getLeoOutlineHelper
def getLeoOutlineHelper (self,s,reassignIndices,checking):

    self.checking = checking
    self.usingClipboard = True
    self.fileBuffer = s ; self.fileBufferIndex = 0
    self.descendentUnknownAttributesDictList = []
    v = None

    self.tnodesDict = {}
    if not reassignIndices:
        << recreate tnodesDict >>
    try:
        self.getXmlVersionTag()
        self.getXmlStylesheetTag()
        self.getTag("<leo_file>")
        self.getClipboardHeader()
        self.getDummyElements()
        self.getVnodes(reassignIndices)
        self.getTnodes()
        self.getTag("</leo_file>")
        if not checking:
            v = self.finishPaste(reassignIndices)
    finally:
        self.fileBuffer = None ; self.fileBufferIndex = 0
        self.usingClipboard = False
        self.tnodesDict = {}
    return v
#@+node:EKR.20040610134756:<< recreate tnodesDict >>
nodeIndices = g.app.nodeIndices

self.tnodesDict = {}

for t in self.c.all_unique_tnodes_iter():
    # Bug fix: Leo 4.4.8: all tnodes have a fileIndex field: make sure it is non-None.
    if hasattr(t,'fileIndex') and t.fileIndex:
        tref = t.fileIndex
        if nodeIndices.isGnx(tref):
            tref = nodeIndices.toString(tref)
        self.tnodesDict[tref] = t

if 0:
    print '-'*40
    for key in self.tnodesDict.keys():
        print key,self.tnodesDict[key]
#@-node:EKR.20040610134756:<< recreate tnodesDict >>
#@-node:ekr.20060826052453.1:getLeoOutlineHelper
#@+node:ekr.20031218072017.2009:newTnode
def newTnode(self,index):

    if self.tnodesDict.has_key(index):
        g.es("bad tnode index:",str(index),"using empty text.")
        return leoNodes.tnode()
    else:
        # Create the tnode.  Use the _original_ index as the key in tnodesDict.
        t = leoNodes.tnode()
        self.tnodesDict[index] = t

        if type(index) not in (type(""),type(u"")):
            g.es("newTnode: unexpected index type:",type(index),index,color="red")

        # Convert any pre-4.1 index to a gnx.
        junk,theTime,junk = gnx = g.app.nodeIndices.scanGnx(index,0)
        if theTime != None:
            t.fileIndex = gnx

        return t
#@-node:ekr.20031218072017.2009:newTnode
#@+node:ekr.20060919110638.7:createSaxVnode
def createSaxVnode (self,node,parent_v,t=None):

    h = node.headString
    b = node.bodyString

    if not t:
        t = leoNodes.tnode(bodyString=b,headString=h)
        if node.tnx:
            t.fileIndex = g.app.nodeIndices.scanGnx(node.tnx,0)
    v = leoNodes.vnode(t)
    v.t.vnodeList.append(v)
    v._parent = parent_v

    index = self.canonicalTnodeIndex(node.tnx)
    self.tnodesDict [index] = t

    # g.trace('tnx','%-22s' % (index),'v',id(v),'v.t',id(v.t),'body','%-4d' % (len(b)),h)

    self.handleVnodeSaxAttributes(node,v)
    self.handleTnodeSaxAttributes(node,t)

    return v
#@nonl
#@+node:ekr.20060919110638.8:handleTnodeSaxAttributes
def handleTnodeSaxAttributes (self,node,t):

    d = node.tnodeAttributes

    aDict = {}
    for key in d.keys():
        val = d.get(key)
        val2 = self.getSaxUa(key,val)
        aDict[key] = val2

    if aDict:
        # g.trace('uA',aDict)
        t.unknownAttributes = aDict
#@nonl
#@-node:ekr.20060919110638.8:handleTnodeSaxAttributes
#@+node:ekr.20061004053644:handleVnodeSaxAttributes
# The native attributes of <v> elements are a, t, vtag, tnodeList,
# marks, expanded and descendentTnodeUnknownAttributes.

def handleVnodeSaxAttributes (self,node,v):

    d = node.attributes
    s = d.get('a')
    if s:
        # g.trace('%s a=%s %s' % (id(node),s,v.headString()))
        # 'C' (clone) and 'D' bits are not used.
        if 'M' in s: v.setMarked()
        if 'E' in s: v.expand()
        if 'O' in s: v.setOrphan()
        if 'T' in s: self.topVnode = v
        if 'V' in s:
            # g.trace('setting currentVnode',v,color='red')
            self.currentVnode = v

    s = d.get('tnodeList','')
    tnodeList = s and s.split(',')
    if tnodeList:
        # This tnode list will be resolved later.
        # g.trace('found tnodeList',v.headString(),tnodeList)
        v.tempTnodeList = tnodeList

    s = d.get('descendentTnodeUnknownAttributes') # Correct: only tnode have descendent uA's.
    if s: 
        aDict = self.getDescendentUnknownAttributes(s)
        if aDict:
            # g.trace('descendentUaDictList',aDict)
            self.descendentUnknownAttributesDictList.append(aDict)

    s = d.get('expanded')
    if s:
        aList = self.getDescendentAttributes(s,tag="expanded")
        # g.trace('expanded list',len(aList))
        self.descendentExpandedList.extend(aList)

    s = d.get('marks')
    if s:
        aList = self.getDescendentAttributes(s,tag="marks")
        # g.trace('marks list',len(aList))
        self.descendentMarksList.extend(aList)

    aDict = {}
    for key in d.keys():
        if key in self.nativeVnodeAttributes:
            if 0: g.trace('****ignoring***',key,d.get(key))
        else:
            val = d.get(key)
            val2 = self.getSaxUa(key,val)
            aDict[key] = val2
            # g.trace(key,val,val2)
    if aDict:
        # g.trace('uA',aDict)
        v.unknownAttributes = aDict
#@nonl
#@-node:ekr.20061004053644:handleVnodeSaxAttributes
#@-node:ekr.20060919110638.7:createSaxVnode
#@-node:ekr.20080310093038.6:To be studied
#@+node:ekr.20031218072017.3020:Reading
#@+node:ekr.20060919104836: Top-level
#@+node:ekr.20070919133659.1:checkLeoFile (fileCommands)
def checkLeoFile (self,event=None):

    fc = self ; c = fc.c ; p = c.currentPosition()

    # Put the body (minus the @nocolor) into the file buffer.
    s = p.bodyString() ; tag = '@nocolor\n'
    if s.startswith(tag): s = s[len(tag):]
    self.fileBuffer = s ; self.fileBufferIndex = 0

    # Do a trial read.
    self.checking = True
    self.initReadIvars()
    c.loading = True # disable c.changed
    try:
        try:
            self.getAllLeoElements(fileName='check-leo-file',silent=False)
            g.es_print('check-leo-file passed',color='blue')
        except BadLeoFile, message:
            # g.es_exception()
            g.es_print('check-leo-file failed:',str(message),color='red')
    finally:
        self.checking = False
        c.loading = False # reenable c.changed
#@-node:ekr.20070919133659.1:checkLeoFile (fileCommands)
#@+node:ekr.20031218072017.1559:getLeoOutlineFromClipboard & helpers
def getLeoOutlineFromClipboard (self,s,reassignIndices=True):

    '''Read a Leo outline from string s in clipboard format.'''

    try:
        v = self.getLeoOutlineHelper(s,reassignIndices,checking=True)
        v = self.getLeoOutlineHelper(s,reassignIndices,checking=False)
    except invalidPaste:
        v = None
        g.es("invalid Paste As Clone",color="blue")
    except BadLeoFile:
        v = None
        g.es("the clipboard is not valid ",color="blue")

    return v

getLeoOutline = getLeoOutlineFromClipboard # for compatibility
#@nonl
#@+node:ekr.20031218072017.1557:finishPaste
def finishPaste(self,reassignIndices=True):

    """Finish pasting an outline from the clipboard.

    Retain clone links if reassignIndices is False."""

    c = self.c
    current = c.currentPosition()
    if reassignIndices:
        << reassign tnode indices >>
    c.selectPosition(current)
    return current
#@+node:ekr.20031218072017.1558:<< reassign tnode indices >>
# We must *reassign* indices here so no "False clones" are created.

nodeIndices = g.app.nodeIndices

current.clearVisitedInTree()

for p in current.self_and_subtree_iter():
    t = p.v.t
    if not t.isVisited():
        t.setVisited()
        t.fileIndex = nodeIndices.getNewIndex()
#@-node:ekr.20031218072017.1558:<< reassign tnode indices >>
#@-node:ekr.20031218072017.1557:finishPaste
#@+node:ekr.20060826052453.1:getLeoOutlineHelper
def getLeoOutlineHelper (self,s,reassignIndices,checking):

    self.checking = checking
    self.usingClipboard = True
    self.fileBuffer = s ; self.fileBufferIndex = 0
    self.descendentUnknownAttributesDictList = []
    v = None

    self.tnodesDict = {}
    if not reassignIndices:
        << recreate tnodesDict >>
    try:
        self.getXmlVersionTag()
        self.getXmlStylesheetTag()
        self.getTag("<leo_file>")
        self.getClipboardHeader()
        self.getDummyElements()
        self.getVnodes(reassignIndices)
        self.getTnodes()
        self.getTag("</leo_file>")
        if not checking:
            v = self.finishPaste(reassignIndices)
    finally:
        self.fileBuffer = None ; self.fileBufferIndex = 0
        self.usingClipboard = False
        self.tnodesDict = {}
    return v
#@+node:EKR.20040610134756:<< recreate tnodesDict >>
nodeIndices = g.app.nodeIndices

self.tnodesDict = {}

for t in self.c.all_unique_tnodes_iter():
    # Bug fix: Leo 4.4.8: all tnodes have a fileIndex field: make sure it is non-None.
    if hasattr(t,'fileIndex') and t.fileIndex:
        tref = t.fileIndex
        if nodeIndices.isGnx(tref):
            tref = nodeIndices.toString(tref)
        self.tnodesDict[tref] = t

if 0:
    print '-'*40
    for key in self.tnodesDict.keys():
        print key,self.tnodesDict[key]
#@-node:EKR.20040610134756:<< recreate tnodesDict >>
#@-node:ekr.20060826052453.1:getLeoOutlineHelper
#@+node:ekr.20031218072017.3022:getClipboardHeader
def getClipboardHeader (self):

    if self.getOpenTag("<leo_header"):
        return # <leo_header> or <leo_header/> has been seen.

    while 1:
        if self.matchTag("file_format="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("tnodes="):
            self.getDquote() ; self.getLong() ; self.getDquote() # no longer used
        elif self.matchTag("max_tnode_index="):
            self.getDquote() ; self.getLong() ; self.getDquote() # no longer used
        elif self.matchTag("></leo_header>"): # new in 4.2: allow this form.
            break
        else:
            self.getTag("/>")
            break
#@nonl
#@-node:ekr.20031218072017.3022:getClipboardHeader
#@-node:ekr.20031218072017.1559:getLeoOutlineFromClipboard & helpers
#@+node:ekr.20031218072017.1553:getLeoFile
# The caller should enclose this in begin/endUpdate.

def getLeoFile (self,theFile,fileName,readAtFileNodesFlag=True,silent=False):

    c = self.c
    c.setChanged(False) # May be set when reading @file nodes.
    << warn on read-only files >>
    self.checking = False
    self.mFileName = c.mFileName
    self.initReadIvars()
    c.loading = True # disable c.changed

    try:
        ok = True
        # t1 = time.clock()
        if self.use_sax:
            v = self.readSaxFile(theFile,fileName,silent)
            if v: # v == None for minimal .leo files.
                c.setRootVnode(v)
                self.rootVnode = v
            else:
                self.rootVnode = c.rootPosition().v
        else:
            self.getAllLeoElements(fileName,silent)
        # t2 = time.clock()
        # g.trace('time',t2-t1)
    except BadLeoFile, message:
        if not silent:
            g.es_exception()
            g.alert(self.mFileName + " is not a valid Leo file: " + str(message))
        ok = False

    # New in Leo 4.2.2: before reading derived files.
    if self.use_sax:
        self.resolveTnodeLists()
    if ok and readAtFileNodesFlag:
        # Redraw before reading the @file nodes so the screen isn't blank.
        # This is important for big files like LeoPy.leo.
        c.redraw_now()
        c.atFileCommands.readAll(c.rootVnode(),partialFlag=False)

    # Do this after reading derived files.
    if readAtFileNodesFlag:
        # The descendent nodes won't exist unless we have read the @thin nodes!
        self.restoreDescendentAttributes()
    if self.use_sax:
        self.setPositionsFromVnodes()
    else:
        if not self.usingClipboard:
            self.setPositionsFromStacks()
        if not c.currentPosition():
            c.setCurrentPosition(c.rootPosition())

    c.selectVnode(c.currentPosition()) # load body pane
    c.loading = False # reenable c.changed
    c.setChanged(c.changed) # Refresh the changed marker.
    self.initReadIvars()
    return ok, self.ratio
#@nonl
#@+node:ekr.20031218072017.1554:<< warn on read-only files >>
# os.access may not exist on all platforms.

try:
    self.read_only = not os.access(fileName,os.W_OK)
except AttributeError:
    self.read_only = False
except UnicodeError:
    self.read_only = False

if self.read_only:
    g.es("read only:",fileName,color="red")
#@-node:ekr.20031218072017.1554:<< warn on read-only files >>
#@-node:ekr.20031218072017.1553:getLeoFile
#@+node:ekr.20031218072017.2009:newTnode
def newTnode(self,index):

    if self.tnodesDict.has_key(index):
        g.es("bad tnode index:",str(index),"using empty text.")
        return leoNodes.tnode()
    else:
        # Create the tnode.  Use the _original_ index as the key in tnodesDict.
        t = leoNodes.tnode()
        self.tnodesDict[index] = t

        if type(index) not in (type(""),type(u"")):
            g.es("newTnode: unexpected index type:",type(index),index,color="red")

        # Convert any pre-4.1 index to a gnx.
        junk,theTime,junk = gnx = g.app.nodeIndices.scanGnx(index,0)
        if theTime != None:
            t.fileIndex = gnx

        return t
#@-node:ekr.20031218072017.2009:newTnode
#@+node:ekr.20031218072017.3029:readAtFileNodes (fileCommands)
def readAtFileNodes (self):

    c = self.c ; p = c.currentPosition()

    c.beginUpdate()
    try:
        c.atFileCommands.readAll(p,partialFlag=True)
    finally:
        c.endUpdate()

    # Force an update of the body pane.
    c.setBodyString(p,p.bodyString())
    c.frame.body.onBodyChanged(undoType=None)
#@-node:ekr.20031218072017.3029:readAtFileNodes (fileCommands)
#@+node:ekr.20031218072017.2297:open (leoFileCommands)
def open(self,theFile,fileName,readAtFileNodesFlag=True,silent=False):

    c = self.c ; frame = c.frame
    if not self.use_sax:
        << read the entire file into the buffer >>
        theFile.close()
        self.fileBufferIndex = 0
    << Set the default directory >>
    self.topPosition = None
    ok, ratio = self.getLeoFile(
        theFile,fileName,
        readAtFileNodesFlag=readAtFileNodesFlag,
        silent=silent)
    frame.resizePanesToRatio(ratio,frame.secondary_ratio)
    if 0: # 1/30/04: this is useless.
        if self.topPosition: 
            c.setTopVnode(self.topPosition)
    if not self.use_sax: # Delete the file buffer
        self.fileBuffer = ""
    return ok
#@nonl
#@+node:ekr.20070412103240:<< read the entire file into the buffer >>
isZipped = zipfile.is_zipfile(fileName)

if isZipped:
    aList = theFile.infolist()
    contentsName = aList[0].filename
    self.fileBuffer = theFile.read(contentsName)
else:
    self.fileBuffer = theFile.read()
#@-node:ekr.20070412103240:<< read the entire file into the buffer >>
#@+node:ekr.20031218072017.2298:<< Set the default directory >>
@ The most natural default directory is the directory containing the .leo file that we are about to open.  If the user has specified the "Default Directory" preference that will over-ride what we are about to set.
@c

theDir = g.os_path_dirname(fileName)

if len(theDir) > 0:
    c.openDirectory = theDir
#@-node:ekr.20031218072017.2298:<< Set the default directory >>
#@-node:ekr.20031218072017.2297:open (leoFileCommands)
#@+node:ekr.20031218072017.3030:readOutlineOnly
def readOutlineOnly (self,theFile,fileName):

    c = self.c
    # Read the entire file into the buffer
    self.fileBuffer = theFile.read() ; theFile.close()
    self.fileBufferIndex = 0
    << Set the default directory >>
    c.beginUpdate()
    try:
        ok, ratio = self.getLeoFile(theFile,fileName,readAtFileNodesFlag=False)
    finally:
        c.endUpdate()
    c.frame.deiconify()
    junk,junk,secondary_ratio = self.frame.initialRatios()
    c.frame.resizePanesToRatio(ratio,secondary_ratio)
    if 0: # 1/30/04: this is useless.
        # This should be done after the pane size has been set.
        if self.topPosition:
            c.frame.tree.setTopPosition(self.topPosition)
            c.redraw_now()
    # delete the file buffer
    self.fileBuffer = ""
    return ok
#@+node:ekr.20071211134300:<< Set the default directory >>
@ The most natural default directory is the directory containing the .leo file that we are about to open.  If the user has specified the "Default Directory" preference that will over-ride what we are about to set.
@c

theDir = g.os_path_dirname(fileName)

if len(theDir) > 0:
    c.openDirectory = theDir
#@-node:ekr.20071211134300:<< Set the default directory >>
#@-node:ekr.20031218072017.3030:readOutlineOnly
#@-node:ekr.20060919104836: Top-level
#@+node:ekr.20060919133249:Common
# Methods common to both the sax and non-sax code.
#@nonl
#@+node:ekr.20031218072017.2004:canonicalTnodeIndex
def canonicalTnodeIndex(self,index):

    """Convert Tnnn to nnn, leaving gnx's unchanged."""

    # index might be Tnnn, nnn, or gnx.
    junk,theTime,junk = g.app.nodeIndices.scanGnx(index,0)
    if theTime == None: # A pre-4.1 file index.
        if index[0] == "T":
            index = index[1:]

    return index
#@-node:ekr.20031218072017.2004:canonicalTnodeIndex
#@+node:ekr.20040701065235.1:getDescendentAttributes
def getDescendentAttributes (self,s,tag=""):

    '''s is a list of gnx's, separated by commas from a <v> or <t> element.
    Parses s into a list.

    This is used to record marked and expanded nodes.
    '''

    # __pychecker__ = '--no-argsused' # tag used only for debugging.

    gnxs = s.split(',')
    result = [gnx for gnx in gnxs if len(gnx) > 0]
    # g.trace(tag,result)
    return result
#@-node:ekr.20040701065235.1:getDescendentAttributes
#@+node:EKR.20040627114602:getDescendentUnknownAttributes
# Only @thin vnodes have the descendentTnodeUnknownAttributes field.
# The question is: what are we to do about this?

def getDescendentUnknownAttributes (self,s):

    try:
        bin = binascii.unhexlify(s) # Throws a TypeError if val is not a hex string.
        val = pickle.loads(bin)
        return val

    except (TypeError,pickle.UnpicklingError,ImportError):
        g.trace('Can not unpickle',s)
        return None
#@-node:EKR.20040627114602:getDescendentUnknownAttributes
#@+node:ekr.20060919142200.1:initReadIvars
def initReadIvars (self):

    self.descendentUnknownAttributesDictList = []
    self.descendentExpandedList = []
    self.descendentMarksList = []
    self.tnodesDict = {}
#@nonl
#@-node:ekr.20060919142200.1:initReadIvars
#@+node:EKR.20040627120120:restoreDescendentAttributes
def restoreDescendentAttributes (self):

    c = self.c ; verbose = True 

    for resultDict in self.descendentUnknownAttributesDictList:
        for gnx in resultDict.keys():
            tref = self.canonicalTnodeIndex(gnx)
            t = self.tnodesDict.get(tref)
            if t:
                t.unknownAttributes = resultDict[gnx]
                t._p_changed = 1
            elif verbose:
                g.trace('can not find tnode (duA): gnx = %s' % gnx,color='red')
    marks = {} ; expanded = {}
    for gnx in self.descendentExpandedList:
        tref = self.canonicalTnodeIndex(gnx)
        t = self.tnodesDict.get(gnx)
        if t: expanded[t]=t
        elif verbose:
            g.trace('can not find tnode (expanded): gnx = %s, tref: %s' % (gnx,tref),color='red')

    for gnx in self.descendentMarksList:
        tref = self.canonicalTnodeIndex(gnx)
        t = self.tnodesDict.get(gnx)
        if t: marks[t]=t
        elif verbose:
            g.trace('can not find tnode (marks): gnx = %s tref: %s' % (gnx,tref),color='red')

    if marks or expanded:
        # g.trace('marks',len(marks),'expanded',len(expanded))
        for p in c.all_positions_iter():
            if marks.get(p.v.t):
                p.v.initMarkedBit()
                    # This was the problem: was p.setMark.
                    # There was a big performance bug in the mark hook in the Node Navigator plugin.
            if expanded.get(p.v.t):
                p.expand()
#@-node:EKR.20040627120120:restoreDescendentAttributes
#@-node:ekr.20060919133249:Common
#@+node:ekr.20031218072017.3021:Non-sax
#@+node:ekr.20040326052245:convertStackToPosition
def convertStackToPosition (self,stack):

    c = self.c ; p2 = None
    if not stack: return None

    for p in c.allNodes_iter():
        if p.v == stack[0]:
            p2 = p.copy()
            for n in xrange(len(stack)):
                if not p2: break
                # g.trace("compare",n,p2.v,stack[n])
                if p2.v != stack[n]:
                    p2 = None
                elif n + 1 == len(stack):
                    break
                else:
                    p2.moveToParent()
            if p2:
                return p

    return None
#@-node:ekr.20040326052245:convertStackToPosition
#@+node:ekr.20031218072017.1243:get, match & skip (basic)
#@+node:ekr.20031218072017.1244:get routines (basic)
#@+node:EKR.20040526204706:getBool
def getBool (self):

    self.skipWs() # guarantees at least one more character.
    ch = self.fileBuffer[self.fileBufferIndex]
    if ch == '0':
        self.fileBufferIndex += 1 ; return False
    elif ch == '1':
        self.fileBufferIndex += 1 ; return True
    else:
        raise BadLeoFile("expecting bool constant")
#@-node:EKR.20040526204706:getBool
#@+node:EKR.20040526204706.1:getDouble
def getDouble (self):

    self.skipWs()
    i = self.fileBufferIndex ; buf = self.fileBuffer
    floatChars = 'eE.+-'
    n = len(buf)
    while i < n and (buf[i].isdigit() or buf[i] in floatChars):
        i += 1
    if i == self.fileBufferIndex:
        raise BadLeoFile("expecting float constant")
    val = float(buf[self.fileBufferIndex:i])
    self.fileBufferIndex = i
    return val
#@-node:EKR.20040526204706.1:getDouble
#@+node:EKR.20040526204706.2:getDqBool
def getDqBool (self):

    self.getDquote()
    val = self.getBool()
    self.getDquote()
    return val
#@-node:EKR.20040526204706.2:getDqBool
#@+node:EKR.20040526204706.3:getDqString
def getDqString (self):

    self.getDquote()
    i = self.fileBufferIndex
    self.fileBufferIndex = j = string.find(self.fileBuffer,'"',i)
    if j == -1: raise BadLeoFile("unterminated double quoted string")
    s = self.fileBuffer[i:j]
    self.getDquote()
    return s
#@-node:EKR.20040526204706.3:getDqString
#@+node:EKR.20040526204706.4:getDquote
def getDquote (self):

    self.getTag('"')
#@-node:EKR.20040526204706.4:getDquote
#@+node:ekr.20031218072017.3024:getEscapedString
def getEscapedString (self):

    # The next '<' begins the ending tag.
    i = self.fileBufferIndex
    self.fileBufferIndex = j = string.find(self.fileBuffer,'<',i)
    if j == -1:
        print self.fileBuffer[i:]
        raise BadLeoFile("unterminated escaped string")
    else:
        # Allocates memory
        return self.xmlUnescape(self.fileBuffer[i:j])
#@-node:ekr.20031218072017.3024:getEscapedString
#@+node:EKR.20040526204706.5:getIndex
def getIndex (self):

    val = self.getLong()
    if val < 0: raise BadLeoFile("expecting index")
    return val
#@-node:EKR.20040526204706.5:getIndex
#@+node:EKR.20040526204706.6:getLong
def getLong (self):

    self.skipWs() # guarantees at least one more character.
    i = self.fileBufferIndex
    if self.fileBuffer[i] == u'-':
        i += 1
    n = len(self.fileBuffer)
    while i < n and self.fileBuffer[i].isdigit():
        i += 1
    if i == self.fileBufferIndex:
        raise BadLeoFile("expecting int constant")
    val = int(self.fileBuffer[self.fileBufferIndex:i])
    self.fileBufferIndex = i
    return val
#@-node:EKR.20040526204706.6:getLong
#@+node:EKR.20040526204706.7:getOpenTag
def getOpenTag (self,tag):

    """
    Look ahead for collapsed tag: tag may or may not end in ">"
    Skips tag and /> if found, otherwise does not alter index.
    Returns True if the closing part was found.
    Throws BadLeoFile if the tag does not exist.
    """

    if tag[-1] == ">":
        # Only the tag itself or a collapsed tag are valid.
        if self.matchTag(tag):
            return False # Not a collapsed tag.
        elif self.matchTag(tag[:-1]):
            # It must be a collapsed tag.
            self.skipWs()
            if self.matchTag("/>"):
                return True
        print "getOpenTag(", tag, ") failed:"
        raise BadLeoFile("expecting" + tag)
    else:
        # The tag need not be followed by "/>"
        if self.matchTag(tag):
            old_index = self.fileBufferIndex
            self.skipWs()
            if self.matchTag("/>"):
                return True
            else:
                self.fileBufferIndex = old_index
                return False
        else:
            print "getOpenTag(", tag, ") failed:"
            raise BadLeoFile("expecting" + tag)
#@-node:EKR.20040526204706.7:getOpenTag
#@+node:EKR.20040526204706.8:getStringToTag
def getStringToTag (self,tag):

    buf = self.fileBuffer
    blen = len(buf) ; tlen = len(tag)
    i = j = self.fileBufferIndex
    while i < blen:
        if tag == buf[i:i+tlen]:
            self.fileBufferIndex = i
            return buf[j:i]
        else: i += 1

    raise BadLeoFile("expecting string terminated by " + tag)
#@-node:EKR.20040526204706.8:getStringToTag
#@+node:EKR.20040526204706.9:getTag
def getTag (self,tag):

    """
    Look ahead for closing />
    Return True if found.
    """

    if self.matchTag(tag):
        return
    else:
        print "getTag(", tag, ") failed:"
        raise BadLeoFile("expecting" + tag)
#@-node:EKR.20040526204706.9:getTag
#@+node:EKR.20040526204036:getUnknownTag
def getUnknownTag(self):

    self.skipWsAndNl() # guarantees at least one more character.
    tag = self.getStringToTag('=')
    if not tag:
        print "getUnknownTag failed"
        raise BadLeoFile("unknown tag not followed by '='")

    self.fileBufferIndex += 1
    val = self.getDqString()
    # g.trace(tag,val)
    return tag,val
#@-node:EKR.20040526204036:getUnknownTag
#@-node:ekr.20031218072017.1244:get routines (basic)
#@+node:ekr.20031218072017.1245:match routines
def matchChar (self,ch):
    self.skipWs() # guarantees at least one more character.
    if ch == self.fileBuffer[self.fileBufferIndex]:
        self.fileBufferIndex += 1 ; return True
    else: return False

# Warning: does not check for end-of-word,
# so caller must match prefixes first.
def matchTag (self,tag):
    self.skipWsAndNl() # guarantees at least one more character.
    i = self.fileBufferIndex
    if tag == self.fileBuffer[i:i+len(tag)]:
        self.fileBufferIndex += len(tag)
        return True
    else:
        return False

def matchTagWordIgnoringCase (self,tag):
    self.skipWsAndNl() # guarantees at least one more character.
    i = self.fileBufferIndex
    tag = string.lower(tag)
    j = g.skip_c_id(self.fileBuffer,i)
    word = self.fileBuffer[i:j]
    word = string.lower(word)
    if tag == word:
        self.fileBufferIndex += len(tag)
        return True
    else:
        return False
#@-node:ekr.20031218072017.1245:match routines
#@+node:ekr.20031218072017.3027:skipWs
def skipWs (self):

    while self.fileBufferIndex < len(self.fileBuffer):
        ch = self.fileBuffer[self.fileBufferIndex]
        if ch == ' ' or ch == '\t':
            self.fileBufferIndex += 1
        else: break

    # The caller is entitled to get the next character.
    if  self.fileBufferIndex >= len(self.fileBuffer):
        raise BadLeoFile("")
#@-node:ekr.20031218072017.3027:skipWs
#@+node:ekr.20031218072017.3028:skipWsAndNl
def skipWsAndNl (self):

    while self.fileBufferIndex < len(self.fileBuffer):
        ch = self.fileBuffer[self.fileBufferIndex]
        if ch == ' ' or ch == '\t' or ch == '\r' or ch == '\n':
            self.fileBufferIndex += 1
        else: break

    # The caller is entitled to get the next character.
    if  self.fileBufferIndex >= len(self.fileBuffer):
        raise BadLeoFile("")
#@-node:ekr.20031218072017.3028:skipWsAndNl
#@+node:ekr.20031218072017.3031:xmlUnescape
def xmlUnescape(self,s):

    if s:
        s = string.replace(s, '\r', '')
        s = string.replace(s, "&lt;", '<')
        s = string.replace(s, "&gt;", '>')
        s = string.replace(s, "&amp;", '&')
    return s
#@-node:ekr.20031218072017.3031:xmlUnescape
#@-node:ekr.20031218072017.1243:get, match & skip (basic)
#@+node:ekr.20031218072017.1555:getAllLeoElements
def getAllLeoElements (self,fileName,silent):
    c = self.c

    self.getXmlVersionTag()
    self.getXmlStylesheetTag()

    self.getTag("<leo_file>") # Must match exactly.
    self.getLeoHeader()
    self.getGlobals()
    self.getPrefs()
    self.getFindPanelSettings()

    # Causes window to appear.
    c.frame.resizePanesToRatio(c.frame.ratio,c.frame.secondary_ratio)
    if not silent and not g.unitTesting:
        g.es("reading:",fileName)

    self.getVnodes()
    self.getTnodes()
    self.getCloneWindows()
    self.getTag("</leo_file>")
#@nonl
#@-node:ekr.20031218072017.1555:getAllLeoElements
#@+node:ekr.20031218072017.3023:getCloneWindows
# For compatibility with old file formats.

def getCloneWindows (self):

    if not self.matchTag("<clone_windows>"):
        return # <clone_windows/> seen.

    while self.matchTag("<clone_window vtag=\"V"):
        self.getLong() ; self.getDquote() ; self.getTag(">")
        if not self.getOpenTag("<global_window_position"):
            self.getTag("<global_window_position")
            self.getPosition()
            self.getTag("/>")
        self.getTag("</clone_window>")
    self.getTag("</clone_windows>")
#@-node:ekr.20031218072017.3023:getCloneWindows
#@+node:ekr.20061209141653:getDummyElements
def getDummyElements (self):

    # New in Leo 4.4.3: Ignore the dummy elements that allow
    # Pasted Leo outlines to be valid .leo files.
    while 1:
        for tag in ('<globals','<preferences','<find_panel_settings'):
            if self.matchTag(tag) and self.matchTag('/>'):
                break
        else:
            break
#@-node:ekr.20061209141653:getDummyElements
#@+node:ekr.20031218072017.2064:getFindPanelSettings
def getFindPanelSettings (self):

    if self.getOpenTag("<find_panel_settings"):
        return # <find_panel_settings/> seen.

    # New in 4.3: ignore all pre-4.3 find settings.
    while 1:
        if   self.matchTag("batch="):           self.getDqBool()
        elif self.matchTag("ignore_case="):     self.getDqBool()
        elif self.matchTag("mark_changes="):    self.getDqBool()
        elif self.matchTag("mark_finds="):      self.getDqBool()
        elif self.matchTag("node_only="):       self.getDqBool()
        elif self.matchTag("pattern_match="):   self.getDqBool()
        elif self.matchTag("reverse="):         self.getDqBool()
        elif self.matchTag("script_change="):   self.getDqBool()
        elif self.matchTag("script_search="):   self.getDqBool()
        elif self.matchTag("search_headline="): self.getDqBool()
        elif self.matchTag("search_body="):     self.getDqBool()
        elif self.matchTag("selection_only="):  self.getDqBool()
        elif self.matchTag("suboutline_only="): self.getDqBool()
        elif self.matchTag("whole_word="):      self.getDqBool()
        elif self.matchTag("wrap="):            self.getDqBool()
        elif self.matchTag(">"): break
        else: self.getUnknownTag() # Ignore all other tags.
    # Allow only <find_string> or <find_string/>
    if self.getOpenTag("<find_string>"): 
        pass
    else:
        self.getEscapedString() ; self.getTag("</find_string>")
    # Allow only <change_string> or <change_string/>
    if self.getOpenTag("<change_string>"): 
        pass
    else:
        self.getEscapedString() ; self.getTag("</change_string>")
    self.getTag("</find_panel_settings>")
#@-node:ekr.20031218072017.2064:getFindPanelSettings
#@+node:ekr.20031218072017.2306:getGlobals
def getGlobals (self):

    if self.getOpenTag("<globals"):
        # <globals/> seen: set reasonable defaults:
        self.ratio = 0.5
        y,x,h,w = 50,50,500,700
    else:
        self.getTag("body_outline_ratio=\"")
        self.ratio = self.getDouble() ; self.getDquote() ; self.getTag(">")

        self.getTag("<global_window_position")
        y,x,h,w = self.getPosition()
        self.getTag("/>")

        self.getTag("<global_log_window_position")
        self.getPosition()
        self.getTag("/>") # no longer used.

        self.getTag("</globals>")

    # Redraw the window before writing into it.
    self.frame.setTopGeometry(w,h,x,y)
    self.frame.deiconify()
    self.frame.lift()
    self.frame.update()
#@-node:ekr.20031218072017.2306:getGlobals
#@+node:ekr.20031218072017.1970:getLeoHeader
def getLeoHeader (self):

    if self.getOpenTag("<leo_header"):
        return # <leo_header/> seen.

    # New in version 1.7: attributes may appear in any order.
    while 1:
        if self.matchTag("file_format="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("tnodes="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("max_tnode_index="):
            self.getDquote() ; self.getLong() ; self.getDquote()
        elif self.matchTag("clone_windows="):
            self.getDquote() ; self.getLong() ; self.getDquote() # no longer used.
        elif self.matchTag("></leo_header>"): # new in 4.2: allow this form.
            break
        else:
            self.getTag("/>")
            break
#@-node:ekr.20031218072017.1970:getLeoHeader
#@+node:ekr.20031218072017.3025:getPosition
def getPosition (self):

    top = left = height = width = 0
    # New in version 1.7: attributes may appear in any order.
    while 1:
        if self.matchTag("top=\""):
            top = self.getLong() ; self.getDquote()
        elif self.matchTag("left=\""):
            left = self.getLong() ; self.getDquote()
        elif self.matchTag("height=\""):
            height = self.getLong() ; self.getDquote()
        elif self.matchTag("width=\""):
            width = self.getLong() ; self.getDquote()
        else: break
    return top, left, height, width
#@-node:ekr.20031218072017.3025:getPosition
#@+node:ekr.20031218072017.2062:getPrefs
# Note: Leo 4.3 does not write these settings to local .leo files.
# Instead, corresponding settings are contained in leoConfig.leo files.

def getPrefs (self):

    c = self.c

    if self.getOpenTag("<preferences"):
        return # <preferences/> seen

    table = (
        ("allow_rich_text",None,None), # Ignored.
        ("tab_width","tab_width",self.getLong),
        ("page_width","page_width",self.getLong),
        ("tangle_bat","tangle_batch_flag",self.getBool),
        ("untangle_bat","untangle_batch_flag",self.getBool),
        ("output_doc_chunks","output_doc_flag",self.getBool),
        ("noweb_flag",None,None), # Ignored.
        ("extended_noweb_flag",None,None), # Ignored.
        ("defaultTargetLanguage","target_language",self.getTargetLanguage),
        ("use_header_flag","use_header_flag",self.getBool))

    done = False
    while 1:
        found = False
        for tag,var,f in table:
            if self.matchTag("%s=" % tag):
                if var:
                    self.getDquote() ; val = f() ; self.getDquote()
                    setattr(c,var,val)
                    # g.trace(var,val)
                else:
                    self.getDqString()
                found = True ; break
        if not found:
            if self.matchTag("/>"):
                done = True ; break
            if self.matchTag(">"):
                break
            else: # New in 4.1: ignore all other tags.
                self.getUnknownTag()

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

    # Must match longer tags before short prefixes.
    for name in g.app.language_delims_dict.keys():
        if self.matchTagWordIgnoringCase(name):
            language = name.replace("/","")
            # self.getDquote()
            return language

    return "c" # default
#@-node:ekr.20031218072017.2063:getTargetLanguage
#@-node:ekr.20031218072017.2062:getPrefs
#@+node:ekr.20031218072017.3026:getSize (not used!)
def getSize (self):

    # New in version 1.7: attributes may appear in any order.
    height = 0 ; width = 0
    while 1:
        if self.matchTag("height=\""):
            height = self.getLong() ; self.getDquote()
        elif self.matchTag("width=\""):
            width = self.getLong() ; self.getDquote()
        else: break
    return height, width
#@-node:ekr.20031218072017.3026:getSize (not used!)
#@+node:ekr.20031218072017.1561:getTnode
def getTnode (self):

    # we have already matched <t.
    index = -1 ; attrDict = {}

    # New in Leo 4.4: support collapsed tnodes.
    if self.matchTag('/>'): # A collapsed tnode.
        return

    # Attributes may appear in any order.
    while 1:
        if self.matchTag("tx="):
            # New for 4.1.  Read either "Tnnn" or "gnx".
            index = self.getDqString()
        elif self.matchTag("rtf=\"1\""): pass # ignored
        elif self.matchTag("rtf=\"0\""): pass # ignored
        elif self.matchTag(">"):         break
        else: # New for 4.0: allow unknown attributes.
            # New in 4.2: allow pickle'd and hexlify'ed values.
            attr,val = self.getUa("tnode")
            if attr: attrDict[attr] = val

    # index might be Tnnn, nnn, or gnx.
    junk,theTime,junk = g.app.nodeIndices.scanGnx(index,0)
    if theTime == None: # A pre-4.1 file index.
        if index[0] == "T":
            index = index[1:]

    index = self.canonicalTnodeIndex(index)
    t = self.tnodesDict.get(index)
    if t:
        << handle unknown attributes >>
        s = self.getEscapedString()
        t.setTnodeText(s,encoding=self.leo_file_encoding)
    else:
        g.es("no tnode with index:",str(index),"the text will be discarded")
    self.getTag("</t>")
#@+node:ekr.20031218072017.1564:<< handle unknown attributes >>
keys = attrDict.keys()
if keys:
    t.unknownAttributes = attrDict
    t._p_changed = 1
    if 0: # For debugging.
        g.es_print("unknown attributes for tnode",color = "blue")
        for key in keys:
            g.es_print('',"%s = %s" % (key,attrDict.get(key)))
#@-node:ekr.20031218072017.1564:<< handle unknown attributes >>
#@-node:ekr.20031218072017.1561:getTnode
#@+node:ekr.20031218072017.2008:getTnodeList (4.0,4.2)
def getTnodeList (self,s):

    """Parse a list of tnode indices in string s."""

    # Remember: entries in the tnodeList correspond to @+node sentinels, _not_ to tnodes!

    fc = self

    indexList = s.split(',') # The list never ends in a comma.
    tnodeList = []
    for index in indexList:
        index = self.canonicalTnodeIndex(index)
        t = fc.tnodesDict.get(index)
        if not t:
            # Not an error: create a new tnode and put it in fc.tnodesDict.
            # g.trace("not allocated: %s" % index)
            t = self.newTnode(index)
        tnodeList.append(t)

    # if tnodeList: g.trace(len(tnodeList))
    return tnodeList
#@-node:ekr.20031218072017.2008:getTnodeList (4.0,4.2)
#@+node:ekr.20031218072017.1560:getTnodes
def getTnodes (self):

    # A slight change: we require a tnodes element.  But Leo always writes this.
    if self.getOpenTag("<tnodes>"):
        return # <tnodes/> seen.

    while self.matchTag("<t"):
        self.getTnode()

    self.getTag("</tnodes>")
#@-node:ekr.20031218072017.1560:getTnodes
#@+node:EKR.20040526204036.1:getUa (non-sax)
# changed for 4.3.

def getUa(self,unused_nodeType):

    """Parse an unknown attribute in a <v> or <t> element."""

    # New in 4.2.  The unknown tag has been pickled and hexlify'd.
    attr,val = self.getUnknownTag()
    # g.trace(attr,repr(val))
    if not attr:
        return None,None

    # New in 4.3: leave string attributes starting with 'str_' alone.
    if attr.startswith('str_') and type(val) == type(''):
        # g.trace(attr,val)
        return attr,val

    # New in 4.3: convert attributes starting with 'b64_' using the base64 conversion.
    if 0: # Not ready yet.
        if attr.startswith('b64_'):
            try: pass
            except Exception: pass

    try:
        binString = binascii.unhexlify(val) # Throws a TypeError if val is not a hex string.
    except TypeError:
        # Assume that Leo 4.1 wrote the attribute.
        # g.trace('4.1 val:',val2)
        return attr,val
    try:
        # No change needed to support protocols.
        val2 = pickle.loads(binString)
        # g.trace('v.3 val:',val2)
        return attr,val2
    except (pickle.UnpicklingError,ImportError):
        return attr,val
    except Exception:
        return attr,val # New in Leo 4.4.5.
#@-node:EKR.20040526204036.1:getUa (non-sax)
#@+node:ekr.20031218072017.1566:getVnode & helpers
# changed for 4.2 & 4.4
def getVnode (self,parent,back,skip,appendToCurrentStack,appendToTopStack):

    v = None
    setCurrent = setExpanded = setMarked = setOrphan = setTop = False
    tref = -1 ; headline = '' ; tnodeList = None ; attrDict = {}

    # we have already matched <v.

    # New in Leo 4.4: support collapsed tnodes.
    if self.matchTag('/>'): # A collapsed vnode.
        v,skip2 = self.createVnode(parent,back,tref,headline,attrDict)
        if self.checking: return None
        else: return v

    while 1:
        if self.matchTag("a=\""):
            << Handle vnode attribute bits >>
        elif self.matchTag("t="):
            # New for 4.1.  Read either "Tnnn" or "gnx".
            tref = index = self.getDqString()
            if self.usingClipboard:
                << raise invalidPaste if the tnode is in self.forbiddenTnodes >>
        elif self.matchTag("vtag=\"V"):
            self.getIndex() ; self.getDquote() # ignored
        elif self.matchTag("tnodeList="):
            s = self.getDqString()
            tnodeList = self.getTnodeList(s) # New for 4.0
        elif self.matchTag("descendentTnodeUnknownAttributes="):
            # New for 4.2, deprecated for 4.3?
            s = self.getDqString()
            theDict = self.getDescendentUnknownAttributes(s)
            if theDict:
                self.descendentUnknownAttributesDictList.append(theDict)
        elif self.matchTag("expanded="): # New in 4.2
            s = self.getDqString()
            self.descendentExpandedList.extend(self.getDescendentAttributes(s,tag="expanded"))
        elif self.matchTag("marks="): # New in 4.2.
            s = self.getDqString()
            self.descendentMarksList.extend(self.getDescendentAttributes(s,tag="marks"))
        elif self.matchTag(">"):
            break
        else: # New for 4.0: allow unknown attributes.
            # New in 4.2: allow pickle'd and hexlify'ed values.
            attr,val = self.getUa("vnode")
            if attr: attrDict[attr] = val
    # Headlines are optional.
    if self.matchTag("<vh>"):
        headline = self.getEscapedString() ; self.getTag("</vh>")
    # g.trace("skip:",skip,"parent:",parent,"back:",back,"headline:",headline)
    if skip:
        v = self.getExistingVnode(tref,headline)
        if v: # Bug fix: 4/18/05: The headline may change during paste as clone.
            v.initHeadString(headline,encoding=self.leo_file_encoding)
    if v is None:
        v,skip2 = self.createVnode(parent,back,tref,headline,attrDict)
        if not self.checking:
            skip = skip or skip2
            if tnodeList:
                v.t.tnodeList = tnodeList # New for 4.0, 4.2: now in tnode.

    if not self.checking:
        << Set the remembered status bits >>

    # Recursively create all nested nodes.
    parent = v ; back = None
    while self.matchTag("<v"):
        append1 = appendToCurrentStack and len(self.currentVnodeStack) == 0
        append2 = appendToTopStack and len(self.topVnodeStack) == 0
        back = self.getVnode(parent,back,skip,
            appendToCurrentStack=append1,appendToTopStack=append2)

    if not self.checking:
        << Append to current or top stack >>

    # End this vnode.
    self.getTag("</v>")
    return v
#@nonl
#@+node:ekr.20031218072017.1567:<< Handle vnode attribute bits  >>
# The a=" has already been seen.
while 1:
    if   self.matchChar('C'): pass # Not used: clone bits are recomputed later.
    elif self.matchChar('D'): pass # Not used.
    elif self.matchChar('E'): setExpanded = True
    elif self.matchChar('M'): setMarked = True
    elif self.matchChar('O'): setOrphan = True
    elif self.matchChar('T'): setTop = True
    elif self.matchChar('V'): setCurrent = True
    else: break

self.getDquote()
#@-node:ekr.20031218072017.1567:<< Handle vnode attribute bits  >>
#@+node:ekr.20041023110111:<< raise invalidPaste if the tnode is in self.forbiddenTnodes >>
# Bug fix in 4.3 a1: make sure we have valid paste.
junk,theTime,junk = g.app.nodeIndices.scanGnx(index,0)
if not theTime and index[0] == "T":
    index = index[1:]

index = self.canonicalTnodeIndex(index)
t = self.tnodesDict.get(index)

if t in self.forbiddenTnodes:
    # g.trace(t)
    raise invalidPaste
#@-node:ekr.20041023110111:<< raise invalidPaste if the tnode is in self.forbiddenTnodes >>
#@+node:ekr.20031218072017.1568:<< Set the remembered status bits >>
if setCurrent:
    self.currentVnodeStack = [v]

if setTop:
    self.topVnodeStack = [v]

if setExpanded:
    v.initExpandedBit()

if setMarked:
    v.initMarkedBit() # 3/25/03: Do not call setMarkedBit here!

if setOrphan:
    v.setOrphan()
#@-node:ekr.20031218072017.1568:<< Set the remembered status bits >>
#@+node:ekr.20040326055828:<< Append to current or top stack >>
if not setCurrent and len(self.currentVnodeStack) > 0 and appendToCurrentStack:
    #g.trace("append current",v)
    self.currentVnodeStack.append(v)

if not setTop and len(self.topVnodeStack) > 0 and appendToTopStack:
    #g.trace("append top",v)
    self.topVnodeStack.append(v)
#@-node:ekr.20040326055828:<< Append to current or top stack >>
#@+node:ekr.20031218072017.1860:createVnode
# (changed for 4.2) sets skip

def createVnode (self,parent,back,tref,headline,attrDict):

    # g.trace(parent,headline)
    v = None ; c = self.c
    # Shared tnodes are placed in the file even if empty.
    if tref == -1:
        t = leoNodes.tnode()
    else:
        tref = self.canonicalTnodeIndex(tref)
        t = self.tnodesDict.get(tref)
        if not t:
            t = self.newTnode(tref)

    if self.checking: return None,False

    if back: # create v after back.
        v = back.insertAfter(t)
    elif parent: # create v as the parent's first child.
        v = parent.insertAsNthChild(0,t)
    else: # create a root vnode
        v = leoNodes.vnode(t)
        v.moveToRoot(oldRoot=None)
        c.setRootVnode(v) # New in Leo 4.4.2.

    if v not in v.t.vnodeList:
        v.t.vnodeList.append(v) # New in 4.2.

    skip = len(v.t.vnodeList) > 1
    v.initHeadString(headline,encoding=self.leo_file_encoding)
    << handle unknown vnode attributes >>
    # g.trace(skip,tref,v,v.t,len(v.t.vnodeList))
    return v,skip
#@nonl
#@+node:ekr.20031218072017.1861:<< handle unknown vnode attributes >>
keys = attrDict.keys()
if keys:
    v.unknownAttributes = attrDict
    v._p_changed = 1

    if 0: # For debugging.
        g.es_print("unknown attributes for",v.headString(),color="blue")
        for key in keys:
            g.es_print('',"%s = %s" % (key,attrDict.get(key)))
#@-node:ekr.20031218072017.1861:<< handle unknown vnode attributes >>
#@-node:ekr.20031218072017.1860:createVnode
#@+node:ekr.20040326063413:getExistingVnode
def getExistingVnode (self,tref,headline):

    assert(tref > -1)
    tref = self.canonicalTnodeIndex(tref)
    t = self.tnodesDict.get(tref)
    try:
        return t.vnodeList[0]
    except (IndexError,AttributeError):
        g.es("missing vnode:",headline,color="red")
        g.es("probably an outline topology error.")
        return None
#@-node:ekr.20040326063413:getExistingVnode
#@-node:ekr.20031218072017.1566:getVnode & helpers
#@+node:ekr.20031218072017.1565:getVnodes
def getVnodes (self,reassignIndices=True):

    c = self.c

    if self.getOpenTag("<vnodes>"):
        return # <vnodes/> seen.

    self.forbiddenTnodes = []
    back = parent = None # This routine _must_ work on vnodes!
    self.currentVnodeStack = []
    self.topVnodeStack = []

    if self.usingClipboard:
        oldRoot = c.rootPosition()
        oldCurrent = c.currentPosition()
        if not reassignIndices:
            << set self.forbiddenTnodes to tnodes than must not be pasted >>

    while self.matchTag("<v"):
        append1 = not self.usingClipboard and len(self.currentVnodeStack) == 0
        append2 = not self.usingClipboard and len(self.topVnodeStack) == 0
        back = self.getVnode(parent,back,skip=False,
            appendToCurrentStack=append1,appendToTopStack=append2)

    if self.usingClipboard and not self.checking:
        # Link in the pasted nodes after the current position.
        newRoot = c.rootPosition()
        c.setRootPosition(oldRoot)
        newRoot.v.linkAfter(oldCurrent.v)
        newCurrent = oldCurrent.copy()
        newCurrent.v = newRoot.v
        c.setCurrentPosition(newCurrent)

    self.getTag("</vnodes>")
#@+node:ekr.20041023105832:<< set self.forbiddenTnodes to tnodes than must not be pasted >>
self.forbiddenTnodes = []

for p in oldCurrent.self_and_parents_iter():
    if p.v.t not in self.forbiddenTnodes:
        self.forbiddenTnodes.append(p.v.t)

# g.trace("forbiddenTnodes",self.forbiddenTnodes)
#@-node:ekr.20041023105832:<< set self.forbiddenTnodes to tnodes than must not be pasted >>
#@-node:ekr.20031218072017.1565:getVnodes
#@+node:ekr.20031218072017.1249:getXmlStylesheetTag
def getXmlStylesheetTag (self):

    """Parses the optional xml stylesheet string, and sets the corresponding config option.

    For example, given: <?xml_stylesheet s?> the config option is s."""

    c = self.c
    tag = "<?xml-stylesheet "

    if self.matchTag(tag):
        s = self.getStringToTag("?>")
        # print "reading:", tag + s + "?>"
        c.frame.stylesheet = s
        self.getTag("?>")
#@-node:ekr.20031218072017.1249:getXmlStylesheetTag
#@+node:ekr.20031218072017.1468:getXmlVersionTag
# Parses the encoding string, and sets self.leo_file_encoding.

def getXmlVersionTag (self):

    self.getTag(g.app.prolog_prefix_string)
    encoding = self.getDqString()
    self.getTag(g.app.prolog_postfix_string)

    if g.isValidEncoding(encoding):
        self.leo_file_encoding = encoding
        # g.trace('self.leo_file_encoding:',encoding, color="blue")
    else:
        g.es("invalid encoding in .leo file:",encoding,color="red")
#@-node:ekr.20031218072017.1468:getXmlVersionTag
#@+node:ekr.20040326054052:setPositionsFromStacks (silly)
def setPositionsFromStacks (self):

    c = self.c

    current = self.convertStackToPosition(self.currentVnodeStack)

    if current:
        # g.trace('using convertStackToPosition',current)
        c.setCurrentPosition(current)
    else:
        # g.trace(self.currentVnodeStack)
        c.setCurrentPosition(c.rootPosition())

    # At present this is useless: the drawing code doesn't set the top position properly.
    if 0:
        top = self.convertStackToPosition(self.topVnodeStack)
        if top:
            c.setTopPosition(top)
#@nonl
#@-node:ekr.20040326054052:setPositionsFromStacks (silly)
#@-node:ekr.20031218072017.3021:Non-sax
#@+node:ekr.20060919104530:Sax
#@+node:ekr.20060919110638.4:createSaxVnodes & helpers
def createSaxVnodes (self, dummyRoot):

    '''**Important**: this method and its helpers are low-level code
    corresponding to link/unlink methods in leoNodes.py.
    Modify this with extreme care.'''

    children = self.createSaxChildren(dummyRoot,parent_v = None)
    firstChild = children and children[0]

    return firstChild
#@nonl
#@+node:ekr.20060919110638.5:createSaxChildren
# node is a saxNodeClass object, parent_v is a vnode.

def createSaxChildren (self, node, parent_v):

    result = []

    for child in node.children:
        tnx = child.tnx
        t = self.tnodesDict.get(tnx)
        if t:
            # A clone.  Create a new clone node, but share the subtree, i.e., the tnode.
            v = self.createSaxVnode(child,parent_v,t=t)
            # g.trace('clone',id(child),child.headString,'t',v.t)
        else:
            v = self.createSaxVnodeTree(child,parent_v)
        result.append(v)

    self.linkSiblings(result)
    if parent_v: self.linkParentAndChildren(parent_v,result)
    return result
#@nonl
#@-node:ekr.20060919110638.5:createSaxChildren
#@+node:ekr.20060919110638.6:createSaxVnodeTree
def createSaxVnodeTree (self,node,parent_v):

    v = self.createSaxVnode(node,parent_v)

    self.createSaxChildren(node,v)

    return v
#@nonl
#@-node:ekr.20060919110638.6:createSaxVnodeTree
#@+node:ekr.20060919110638.7:createSaxVnode
def createSaxVnode (self,node,parent_v,t=None):

    h = node.headString
    b = node.bodyString

    if not t:
        t = leoNodes.tnode(bodyString=b,headString=h)
        if node.tnx:
            t.fileIndex = g.app.nodeIndices.scanGnx(node.tnx,0)
    v = leoNodes.vnode(t)
    v.t.vnodeList.append(v)
    v._parent = parent_v

    index = self.canonicalTnodeIndex(node.tnx)
    self.tnodesDict [index] = t

    # g.trace('tnx','%-22s' % (index),'v',id(v),'v.t',id(v.t),'body','%-4d' % (len(b)),h)

    self.handleVnodeSaxAttributes(node,v)
    self.handleTnodeSaxAttributes(node,t)

    return v
#@nonl
#@+node:ekr.20060919110638.8:handleTnodeSaxAttributes
def handleTnodeSaxAttributes (self,node,t):

    d = node.tnodeAttributes

    aDict = {}
    for key in d.keys():
        val = d.get(key)
        val2 = self.getSaxUa(key,val)
        aDict[key] = val2

    if aDict:
        # g.trace('uA',aDict)
        t.unknownAttributes = aDict
#@nonl
#@-node:ekr.20060919110638.8:handleTnodeSaxAttributes
#@+node:ekr.20061004053644:handleVnodeSaxAttributes
# The native attributes of <v> elements are a, t, vtag, tnodeList,
# marks, expanded and descendentTnodeUnknownAttributes.

def handleVnodeSaxAttributes (self,node,v):

    d = node.attributes
    s = d.get('a')
    if s:
        # g.trace('%s a=%s %s' % (id(node),s,v.headString()))
        # 'C' (clone) and 'D' bits are not used.
        if 'M' in s: v.setMarked()
        if 'E' in s: v.expand()
        if 'O' in s: v.setOrphan()
        if 'T' in s: self.topVnode = v
        if 'V' in s:
            # g.trace('setting currentVnode',v,color='red')
            self.currentVnode = v

    s = d.get('tnodeList','')
    tnodeList = s and s.split(',')
    if tnodeList:
        # This tnode list will be resolved later.
        # g.trace('found tnodeList',v.headString(),tnodeList)
        v.tempTnodeList = tnodeList

    s = d.get('descendentTnodeUnknownAttributes') # Correct: only tnode have descendent uA's.
    if s: 
        aDict = self.getDescendentUnknownAttributes(s)
        if aDict:
            # g.trace('descendentUaDictList',aDict)
            self.descendentUnknownAttributesDictList.append(aDict)

    s = d.get('expanded')
    if s:
        aList = self.getDescendentAttributes(s,tag="expanded")
        # g.trace('expanded list',len(aList))
        self.descendentExpandedList.extend(aList)

    s = d.get('marks')
    if s:
        aList = self.getDescendentAttributes(s,tag="marks")
        # g.trace('marks list',len(aList))
        self.descendentMarksList.extend(aList)

    aDict = {}
    for key in d.keys():
        if key in self.nativeVnodeAttributes:
            if 0: g.trace('****ignoring***',key,d.get(key))
        else:
            val = d.get(key)
            val2 = self.getSaxUa(key,val)
            aDict[key] = val2
            # g.trace(key,val,val2)
    if aDict:
        # g.trace('uA',aDict)
        v.unknownAttributes = aDict
#@nonl
#@-node:ekr.20061004053644:handleVnodeSaxAttributes
#@-node:ekr.20060919110638.7:createSaxVnode
#@+node:ekr.20060919110638.9:linkParentAndChildren
def linkParentAndChildren (self, parent_v, children):

    # if children: g.trace(parent_v,len(children))

    firstChild_v = children and children[0] or None

    parent_v.t._firstChild = firstChild_v

    for child in children:
        child._parent = parent_v

    v = parent_v
    if v not in v.t.vnodeList:
        v.t.vnodeList.append(v)
#@nonl
#@-node:ekr.20060919110638.9:linkParentAndChildren
#@+node:ekr.20060919110638.10:linkSiblings
def linkSiblings (self, sibs):

    '''Set the v._back and v._next links for all vnodes v in sibs.'''

    n = len(sibs)

    for i in xrange(n):
        v = sibs[i]
        v._back = (i-1 >= 0 and sibs[i-1]) or None
        v._next = (i+1 <  n and sibs[i+1]) or None
#@nonl
#@-node:ekr.20060919110638.10:linkSiblings
#@-node:ekr.20060919110638.4:createSaxVnodes & helpers
#@+node:ekr.20060919110638.2:dumpSaxTree
def dumpSaxTree (self,root,dummy):

    if not root:
        print 'dumpSaxTree: empty tree'
        return
    if not dummy:
        root.dump()
    for child in root.children:
        self.dumpSaxTree(child,dummy=False)
#@nonl
#@-node:ekr.20060919110638.2:dumpSaxTree
#@+node:ekr.20061003093021:getSaxUa
def getSaxUa(self,attr,val):

    """Parse an unknown attribute in a <v> or <t> element.
    The unknown tag has been pickled and hexlify'd.
    """

    try:
        val = str(val)
    except UnicodeError:
        g.es_print('unexpected exception converting hexlified string to string')
        g.es_exception()

    # g.trace(attr,repr(val))

    # New in 4.3: leave string attributes starting with 'str_' alone.
    if attr.startswith('str_') and type(val) == type(''):
        # g.trace(attr,val)
        return val

    # New in 4.3: convert attributes starting with 'b64_' using the base64 conversion.
    if 0: # Not ready yet.
        if attr.startswith('b64_'):
            try: pass
            except Exception: pass

    try:
        binString = binascii.unhexlify(val) # Throws a TypeError if val is not a hex string.
    except TypeError:
        # Assume that Leo 4.1 wrote the attribute.
        g.trace('can not unhexlify',val)
        return val
    try:
        # No change needed to support protocols.
        val2 = pickle.loads(binString)
        # g.trace('v.3 val:',val2)
        return val2
    except (pickle.UnpicklingError,ImportError):
        g.trace('can not unpickle',val)
        return val
#@-node:ekr.20061003093021:getSaxUa
#@+node:ekr.20060919110638.14:parse_leo_file
def parse_leo_file (self,theFile,inputFileName,silent):

    c = self.c

    try:
        # Use cStringIo to avoid a crash in sax when inputFileName has unicode characters.
        s = theFile.read()
        theFile = cStringIO.StringIO(s)
        # g.trace(repr(inputFileName))
        node = None
        parser = xml.sax.make_parser()
        parser.setFeature(xml.sax.handler.feature_external_ges,1)
            # Include external general entities, esp. xml-stylesheet lines.
        if 0: # Expat does not read external features.
            parser.setFeature(xml.sax.handler.feature_external_pes,1)
                # Include all external parameter entities
                # Hopefully the parser can figure out the encoding from the <?xml> element.
        handler = saxContentHandler(c,inputFileName,silent)
        parser.setContentHandler(handler)
        parser.parse(theFile) # expat does not support parseString
        node = handler.getRootNode()
    except xml.sax.SAXParseException:
        g.es_print('error parsing',inputFileName,color='red')
        g.es_exception()
    except Exception:
        g.es_print('unexpected exception parsing',inputFileName,color='red')
        g.es_exception()

    return node
#@nonl
#@-node:ekr.20060919110638.14:parse_leo_file
#@+node:ekr.20060919110638.3:readSaxFile
def readSaxFile (self,theFile,fileName,silent):

    # Pass one: create the intermediate nodes.
    dummyRoot = self.parse_leo_file(theFile,fileName,silent=silent)

    # self.dumpSaxTree(dummyRoot,dummy=True)

    # Pass two: create the tree of vnodes and tnodes from the intermediate nodes.
    v = dummyRoot and self.createSaxVnodes(dummyRoot)
    return v
#@nonl
#@-node:ekr.20060919110638.3:readSaxFile
#@+node:ekr.20060919110638.11:resolveTnodeLists
def resolveTnodeLists (self):

    c = self.c

    for p in c.allNodes_iter():
        if hasattr(p.v,'tempTnodeList'):
            # g.trace(p.v.headString())
            result = []
            for tnx in p.v.tempTnodeList:
                index = self.canonicalTnodeIndex(tnx)
                t = self.tnodesDict.get(index)
                if t:
                    # g.trace(tnx,t)
                    result.append(t)
                else:
                    g.trace('No tnode for %s' % tnx)
            p.v.t.tnodeList = result
            delattr(p.v,'tempTnodeList')
#@nonl
#@-node:ekr.20060919110638.11:resolveTnodeLists
#@+node:ekr.20060919110638.13:setPositionsFromVnodes & helper
def setPositionsFromVnodes (self):

    c = self.c ; p = c.rootPosition()

    current = None
    d = hasattr(p.v,'unknownAttributes') and p.v.unknownAttributes
    if d:
        s = d.get('str_leo_pos')
        if s:
            current = self.archivedPositionToPosition(s)

    c.setCurrentPosition(current or c.rootPosition())
#@nonl
#@+node:ekr.20061006104837.1:archivedPositionToPosition
def archivedPositionToPosition (self,s):

    c = self.c
    aList = s.split(',')
    try:
        aList = [int(z) for z in aList]
    except Exception:
        g.trace('oops: bad archived position:',aList)
        aList = None
    if not aList: return None
    p = c.rootPosition() ; level = 0
    while level < len(aList):
        i = aList[level]
        while i > 0:
            if p.hasNext():
                p.moveToNext()
                i -= 1
            else:
                g.trace('oops: bad archived position:',aList)
                return None
        level += 1
        if level < len(aList):
            p.moveToFirstChild()
            # g.trace('level',level,'index',aList[level],p.headString())
    return p
#@nonl
#@-node:ekr.20061006104837.1:archivedPositionToPosition
#@-node:ekr.20060919110638.13:setPositionsFromVnodes & helper
#@-node:ekr.20060919104530:Sax
#@-node:ekr.20031218072017.3020:Reading
#@-node:ekr.20080310072732.1:Made sure that all fileIndex fields get converted on read
#@+node:ekr.20080311060457.219:Fixed distribution problems
@nocolor

- line endings on install, uninstall scripts.
- (done) rename setup.py to createLeoDistribution.py.
- (done) do not distribute createLeoDistribution.py.
- (done) copy readme and install files to trunk folder, and erase them after the install.

On Tue, Mar 11, 2008 at 9:39 AM, John Griessen <john@cibolo.com> wrote:

    I just used bzr to get rev 118, then to install, all I needed to do was make install executable
    and change /usr/local to suit.

    Adding a file such as INSTALL would still help newbies, since I had to discover that
    the install script wants to be used this way:

    cd to leo
    edit script
    make script executable
    ../install

On Mon, Mar 10, 2008 at 4:57 PM, John Griessen <john@cibolo.com> wrote:

    maphew wrote:

    > following another message regarding line endings I tried converting
    > them for 'install', with this result:
    >
    >    $ dos2unix install
    >    $ sudo ./install
    >
    >    Prefix directory set to "/usr/local"
    >
    >    Making directory structure /usr/local/lib/leo
    >    cp: cannot stat `src': No such file or directory
    >    cp: cannot stat `config': No such file or directory
    >
    >    Leo installed successfully
    >    Make sure /usr/local/bin is in your path then
    >    type 'leo filename.leo' to use it.


    I looked at the install script and saw it wants you to be in
    dir   leo-4.4.8-b1/leo

    and then run ../install

    I wrote this documentation file.  I release it under python license
    to use by all.

    It seems to be just a documentation problem...or maybe a dir structure change?
    =================
    #INSTALL
    #
    How to install leo on *NIX systems.
    Method 1.  Install a startup script and the source python in a place on your filesystem
           intended for user programs.
    Use the install script.  change dir to the unpacked leo distribution dir, e.g. leo-4.4.8-b1, the to the subdir ./leo.
    make the install file executable -- chmod 754 install.
    edit the settings in the first few lines to suit you.  One place to install is in /usr/local,
    others could be /opt/leo, etc.

    execute the file

    ../install

    ==========


Thanks for this.  It will be part of the new, improved Leo distribution.

Edward

#@-node:ekr.20080311060457.219:Fixed distribution problems
#@-node:None.None:Bug fixes
#@+node:ekr.20080310093038.1:Features
#@+node:ekr.20080229073116:Allow arguments to minibuffer commands
@nocolor

http://mail.google.com/mail/#inbox/1184b70ea10f5aa3

Leo now contains minimal support for arguments to minibuffer commands.

- k.simulateCommand now puts arguments following the command name in k.givenArgs.
  Whitespace separates arguments.

- k.simulateCommand knows nothing about what the arguments mean. That is up to
  the individual commands.

- At present, only the following minibuffer commands now support k.givenArgs:
  open-outline, open-outline-by-name, save-file, save-file-as,
  save-file-as-unzipped and save-file-as-zipped.

  These commands use ''.join(k.givenArgs) to get the file name,
  which may cause problems if the file name contains whitespace.

- At present, the repeat-complex-command command does not support arguments.

@color
#@+node:ekr.20031218072017.2820:top level (file menu)
#@+node:ekr.20031218072017.1623:new
def new (self,event=None,gui=None):

    '''Create a new Leo window.'''

    c,frame = g.app.newLeoCommanderAndFrame(fileName=None,relativeFileName=None,gui=gui)

    # Needed for plugins.
    g.doHook("new",old_c=self,c=c,new_c=c)
    # Use the config params to set the size and location of the window.
    c.beginUpdate()
    try:
        frame.setInitialWindowGeometry()
        frame.deiconify()
        frame.lift()
        frame.resizePanesToRatio(frame.ratio,frame.secondary_ratio) # Resize the _new_ frame.
        t = leoNodes.tnode()
        v = leoNodes.vnode(t)
        p = leoNodes.position(v,[])
        v.initHeadString("NewHeadline")
        v.moveToRoot(oldRoot=None)
        c.setRootVnode(v) # New in Leo 4.4.2.
        c.editPosition(p)
        # New in Leo 4.4.8: create the menu as late as possible so it can use user commands.
        p = c.currentPosition()
        if not g.doHook("menu1",c=c,p=p,v=p):
            frame.menu.createMenuBar(frame)
            c.updateRecentFiles(fileName=None)
            g.doHook("menu2",c=frame.c,p=p,v=p)
            g.doHook("after-create-leo-frame",c=c)

    finally:
        c.endUpdate()
        # chapterController.finishCreate must be called after the first real redraw
        # because it requires a valid value for c.rootPosition().
        if c.config.getBool('use_chapters') and c.chapterController:
            c.chapterController.finishCreate()
            frame.c.setChanged(False) # Clear the changed flag set when creating the @chapters node.
        if c.config.getBool('outline_pane_has_initial_focus'):
            c.treeWantsFocusNow()
        else:
            c.bodyWantsFocusNow()
    return c # For unit test.
#@-node:ekr.20031218072017.1623:new
#@+node:ekr.20031218072017.2821:open
def open (self,event=None):

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

    c = self
    << Set closeFlag if the only open window is empty >>

    fileName = ''.join(c.k.givenArgs) or g.app.gui.runOpenFileDialog(
        title = "Open",
        filetypes = [("Leo files","*.leo"), ("All files","*")],
        defaultextension = ".leo")
    c.bringToFront()

    ok = False
    if fileName and len(fileName) > 0:
        ok, frame = g.openWithFileName(fileName,c)
        if ok:
            g.setGlobalOpenDir(fileName)
        if ok and closeFlag:
            g.app.destroyWindow(c.frame)

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

closeFlag = (
    c.frame.startupWindow and # The window was open on startup
    not c.changed and not c.frame.saved and # The window has never been changed
    g.app.numberOfWindows == 1) # Only one untitled window has ever been opened
#@-node:ekr.20031218072017.2822:<< Set closeFlag if the only open window is empty >>
#@-node:ekr.20031218072017.2821:open
#@+node:ekr.20031218072017.2823:openWith and allies
def openWith(self,event=None,data=None):

    """This routine handles the items in the Open With... menu.

    These items can only be created by createOpenWithMenuFromTable().
    Typically this would be done from the "open2" hook.

    New in 4.3: The "os.spawnv" now works. You may specify arguments to spawnv
    using a list, e.g.:

    openWith("os.spawnv", ["c:/prog.exe","--parm1","frog","--switch2"], None)
    """

    c = self ; p = c.currentPosition()
    n = data and len(data) or 0
    if n != 3:
        g.trace('bad data, length must be 3, got %d' % n)
        return
    try:
        openType,arg,ext=data
        if not g.doHook("openwith1",c=c,p=p,v=p.v,openType=openType,arg=arg,ext=ext):
            g.enableIdleTimeHook(idleTimeDelay=100)
            << set ext based on the present language >>
            << create or reopen temp file, testing for conflicting changes >>
            << execute a command to open path in external editor >>
        g.doHook("openwith2",c=c,p=p,v=p.v,openType=openType,arg=arg,ext=ext)
    except Exception:
        g.es("unexpected exception in c.openWith")
        g.es_exception()

    return "break"
#@+node:ekr.20031218072017.2824:<< set ext based on the present language >>
if not ext:
    theDict = g.scanDirectives(c)
    language = theDict.get("language")
    ext = g.app.language_extension_dict.get(language)
    # print language,ext
    if ext == None:
        ext = "txt"

if ext[0] != ".":
    ext = "."+ext

# print "ext",ext
#@-node:ekr.20031218072017.2824:<< set ext based on the present language >>
#@+node:ekr.20031218072017.2825:<< create or reopen temp file, testing for conflicting changes >>
theDict = None ; path = None
<< set dict and path if a temp file already refers to p.v.t >>
if path:
    << create or recreate temp file as needed >>
else:
    path = c.createOpenWithTempFile(p,ext)

if not path:
    return # An error has occured.
#@+node:ekr.20031218072017.2826:<<set dict and path if a temp file already refers to p.v.t >>
searchPath = c.openWithTempFilePath(p,ext)

if g.os_path_exists(searchPath):
    for theDict in g.app.openWithFiles:
        if p.v == theDict.get('v') and searchPath == theDict.get("path"):
            path = searchPath
            break
#@-node:ekr.20031218072017.2826:<<set dict and path if a temp file already refers to p.v.t >>
#@+node:ekr.20031218072017.2827:<< create or recreate temp file as needed >>
@ We test for changes in both p and the temp file:

- If only p's body text has changed, we recreate the temp file.
- If only the temp file has changed, do nothing here.
- If both have changed we must prompt the user to see which code to use.
@c

encoding = theDict.get("encoding")
old_body = theDict.get("body")
new_body = p.bodyString()
new_body = g.toEncodedString(new_body,encoding,reportErrors=True)

old_time = theDict.get("time")
try:
    new_time = g.os_path_getmtime(path)
except:
    new_time = None

body_changed = old_body != new_body
temp_changed = old_time != new_time

if body_changed and temp_changed:
    << Raise dialog about conflict and set result >>
    if result == "cancel": return
    rewrite = result == "outline"
else:
    rewrite = body_changed

if rewrite:
    path = c.createOpenWithTempFile(p,ext)
else:
    g.es("reopening:",g.shortFileName(path),color="blue")
#@+node:ekr.20031218072017.2828:<< Raise dialog about conflict and set result >>
message = (
    "Conflicting changes in outline and temp file\n\n" +
    "Do you want to use the code in the outline or the temp file?\n\n")

result = g.app.gui.runAskYesNoCancelDialog(c,
    "Conflict!", message,
    yesMessage = "Outline",
    noMessage = "File",
    defaultButton = "Cancel")
#@-node:ekr.20031218072017.2828:<< Raise dialog about conflict and set result >>
#@-node:ekr.20031218072017.2827:<< create or recreate temp file as needed >>
#@-node:ekr.20031218072017.2825:<< create or reopen temp file, testing for conflicting changes >>
#@+node:ekr.20031218072017.2829:<< execute a command to open path in external editor >>
try:
    if arg == None: arg = ""
    shortPath = path # g.shortFileName(path)
    if openType == "os.system":
        if 1:
            # This works, _provided_ that arg does not contain blanks.  Sheesh.
            command = 'os.system(%s)' % (arg+shortPath)
            os.system(arg+shortPath)
        else:
            # XP does not like this format!
            command = 'os.system("%s" "%s")' % (arg,shortPath)
            os.system('"%s" "%s"' % (arg,shortPath))
    elif openType == "os.startfile":
        command = "os.startfile(%s)" % (arg+shortPath)
        os.startfile(arg+path)
    elif openType == "exec":
        command = "exec(%s)" % (arg+shortPath)
        exec arg+path in {}
    elif openType == "os.spawnl":
        filename = g.os_path_basename(arg)
        command = "os.spawnl(%s,%s,%s)" % (arg,filename,path)
        apply(os.spawnl,(os.P_NOWAIT,arg,filename,path))
    elif openType == "os.spawnv":
        filename = os.path.basename(arg[0]) 
        vtuple = arg[1:]
        vtuple.insert(0, filename)
            # add the name of the program as the first argument.
            # Change suggested by Jim Sizelove.
        vtuple.append(path)
        command = "os.spawnv(%s,%s)" % (arg[0],repr(vtuple))
        apply(os.spawnv,(os.P_NOWAIT,arg[0],vtuple))
    # This clause by Jim Sizelove.
    elif openType == "subprocess.Popen":
        if isinstance(arg, basestring):
            vtuple = arg + " " + path
        elif isinstance(arg, (list, tuple)):
            vtuple = arg[:]
            vtuple.append(path)
        command = "subprocess.Popen(%s)" % repr(vtuple)
        if subprocess:
            subprocess.Popen(vtuple)
        else:
            g.trace('Can not import subprocess.  Skipping: "%s"' % command)
    else:
        command="bad command:"+str(openType)
        g.trace(command)
except Exception:
    g.es("exception executing:",command)
    g.es_exception()
#@-node:ekr.20031218072017.2829:<< execute a command to open path in external editor >>
#@+node:ekr.20031218072017.2830:createOpenWithTempFile
def createOpenWithTempFile (self,p,ext):

    c = self
    path = c.openWithTempFilePath(p,ext)
    try:
        if g.os_path_exists(path):
            g.es("recreating:  ",g.shortFileName(path),color="red")
        else:
            g.es("creating:  ",g.shortFileName(path),color="blue")
        theFile = open(path,"w")
        # Convert s to whatever encoding is in effect.
        s = p.bodyString()
        theDict = g.scanDirectives(c,p=p)
        encoding = theDict.get("encoding",None)
        if encoding == None:
            encoding = c.config.default_derived_file_encoding
        s = g.toEncodedString(s,encoding,reportErrors=True) 
        theFile.write(s)
        theFile.flush()
        theFile.close()
        try:    time = g.os_path_getmtime(path)
        except: time = None
        # g.es("time: " + str(time))
        # New in 4.3: theDict now contains both 'p' and 'v' entries, of the expected type.
        theDict = {
            "body":s, "c":c, "encoding":encoding,
            "f":theFile, "path":path, "time":time,
            "p":p, "v":p.v }
        << remove previous entry from app.openWithFiles if it exists >>
        g.app.openWithFiles.append(theDict)
        return path
    except:
        if theFile:
            theFile.close()
        theFile = None
        g.es("exception creating temp file",color="red")
        g.es_exception()
        return None
#@+node:ekr.20031218072017.2831:<< remove previous entry from app.openWithFiles if it exists >>
for d in g.app.openWithFiles[:]:
    p2 = d.get("p")
    if p.v.t == p2.v.t:
        # print "removing previous entry in g.app.openWithFiles for",p.headString()
        g.app.openWithFiles.remove(d)
#@-node:ekr.20031218072017.2831:<< remove previous entry from app.openWithFiles if it exists >>
#@-node:ekr.20031218072017.2830:createOpenWithTempFile
#@+node:ekr.20031218072017.2832:c.openWithTempFilePath
def openWithTempFilePath (self,p,ext):

    """Return the path to the temp file corresponding to p and ext."""

    name = "LeoTemp_%s_%s%s" % (
        str(id(p.v.t)),
        g.sanitize_filename(p.headString()),
        ext)

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

    td = g.os_path_abspath(tempfile.gettempdir())

    path = g.os_path_join(td,name)

    return path
#@-node:ekr.20031218072017.2832:c.openWithTempFilePath
#@-node:ekr.20031218072017.2823:openWith and allies
#@+node:ekr.20031218072017.2833:close
def close (self,event=None):

    '''Close the Leo window, prompting to save it if it has been changed.'''

    g.app.closeLeoWindow(self.frame)
#@-node:ekr.20031218072017.2833:close
#@+node:ekr.20031218072017.2834:save (commands)
def save (self,event=None):

    '''Save a Leo outline to a file.'''

    c = self ; w = g.app.gui.get_focus(c)

    if g.app.disableSave:
        g.es("save commands disabled",color="purple")
        return

    # Make sure we never pass None to the ctor.
    if not c.mFileName:
        c.frame.title = ""
        c.mFileName = ""

    c.beginUpdate()
    try:
        if c.mFileName:
            # Calls c.setChanged(False) if no error.
            c.fileCommands.save(c.mFileName)
        else:
            fileName = ''.join(c.k.givenArgs) or g.app.gui.runSaveFileDialog(
                initialfile = c.mFileName,
                title="Save",
                filetypes=[("Leo files", "*.leo")],
                defaultextension=".leo")
            c.bringToFront()

            if fileName:
                # Don't change mFileName until the dialog has suceeded.
                c.mFileName = g.ensure_extension(fileName, ".leo")
                c.frame.title = c.mFileName
                c.frame.setTitle(g.computeWindowTitle(c.mFileName))
                c.frame.openDirectory = g.os_path_dirname(c.mFileName) # Bug fix in 4.4b2.
                c.fileCommands.save(c.mFileName)
                c.updateRecentFiles(c.mFileName)
    finally:
        c.endUpdate()
        c.widgetWantsFocus(w)
#@nonl
#@-node:ekr.20031218072017.2834:save (commands)
#@+node:ekr.20031218072017.2835:saveAs
def saveAs (self,event=None):

    '''Save a Leo outline to a file with a new filename.'''

    c = self ;  w = g.app.gui.get_focus(c)

    if g.app.disableSave:
        g.es("save commands disabled",color="purple")
        return

    c.beginUpdate()
    try:
        # Make sure we never pass None to the ctor.
        if not c.mFileName:
            c.frame.title = ""

        fileName = ''.join(c.k.givenArgs) or g.app.gui.runSaveFileDialog(
            initialfile = c.mFileName,
            title="Save As",
            filetypes=[("Leo files", "*.leo")],
            defaultextension=".leo")
        c.bringToFront()

        if fileName:
            g.trace(fileName)
            # 7/2/02: don't change mFileName until the dialog has suceeded.
            c.mFileName = g.ensure_extension(fileName, ".leo")
            c.frame.title = c.mFileName
            c.frame.setTitle(g.computeWindowTitle(c.mFileName))
            c.frame.openDirectory = g.os_path_dirname(c.mFileName) # Bug fix in 4.4b2.
            # Calls c.setChanged(False) if no error.
            c.fileCommands.saveAs(c.mFileName)
            c.updateRecentFiles(c.mFileName)
    finally:
        c.endUpdate()
        c.widgetWantsFocus(w)
#@-node:ekr.20031218072017.2835:saveAs
#@+node:ekr.20070413045221:saveAsUnzipped & saveAsZipped
def saveAsUnzipped (self,event=None):

    '''Save a Leo outline to a file with a new filename,
    ensuring that the file is not compressed.'''
    self.saveAsZippedHelper(False)

def saveAsZipped (self,event=None):

    '''Save a Leo outline to a file with a new filename,
    ensuring that the file is compressed.'''
    self.saveAsZippedHelper(True)

def saveAsZippedHelper (self,isZipped):

    c = self
    oldZipped = c.isZipped
    c.isZipped = isZipped
    try:
        c.saveAs()
    finally:
        c.isZipped = oldZipped
#@-node:ekr.20070413045221:saveAsUnzipped & saveAsZipped
#@+node:ekr.20031218072017.2836:saveTo
def saveTo (self,event=None):

    '''Save a Leo outline to a file, leaving the file associated with the Leo outline unchanged.'''

    c = self ; w = g.app.gui.get_focus(c)

    if g.app.disableSave:
        g.es("save commands disabled",color="purple")
        return

    c.beginUpdate()
    try:
        # Make sure we never pass None to the ctor.
        if not c.mFileName:
            c.frame.title = ""

        # set local fileName, _not_ c.mFileName
        fileName = ''.join(c.k.givenArgs) or g.app.gui.runSaveFileDialog(
            initialfile = c.mFileName,
            title="Save To",
            filetypes=[("Leo files", "*.leo")],
            defaultextension=".leo")
        c.bringToFront()

        if fileName:
            fileName = g.ensure_extension(fileName, ".leo")
            c.fileCommands.saveTo(fileName)
            c.updateRecentFiles(fileName)

    finally:
        c.endUpdate()
        c.widgetWantsFocus(w)
#@-node:ekr.20031218072017.2836:saveTo
#@+node:ekr.20031218072017.2837:revert
def revert (self,event=None):

    '''Revert the contents of a Leo outline to last saved contents.'''

    c = self

    # Make sure the user wants to Revert.
    if not c.mFileName:
        return

    reply = g.app.gui.runAskYesNoDialog(c,"Revert",
        "Revert to previous version of " + c.mFileName + "?")
    c.bringToFront()

    if reply=="no":
        return

    # Kludge: rename this frame so openWithFileName won't think it is open.
    fileName = c.mFileName ; c.mFileName = ""

    # Create a new frame before deleting this frame.
    ok, frame = g.openWithFileName(fileName,c)
    if ok:
        frame.deiconify()
        g.app.destroyWindow(c.frame)
    else:
        c.mFileName = fileName
#@-node:ekr.20031218072017.2837:revert
#@-node:ekr.20031218072017.2820:top level (file menu)
#@+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.20061031131434.78:<< define externally visible ivars >>
self.abbrevOn = False # True: abbreviations are on.
self.arg = '' # The value returned by k.getArg.
self.commandName = None # The name of the command being executed.
self.funcReturn = None # For k.simulateCommand
self.getArgEscape = None # A signal that the user escaped getArg in an unusual way.
self.givenArgs = [] # New in Leo 4.4.8: arguments specified after the command name in k.simulateCommand.
self.inputModeBindings = {}
self.inputModeName = '' # The name of the input mode, or None.
self.inverseCommandsDict = {}
    # Completed in k.finishCreate, but leoCommands.getPublicCommands adds entries first.
self.negativeArg = False
self.newMinibufferWidget = None # Usually the minibuffer restores focus.  This overrides this default.
self.regx = g.bunch(iter=None,key=None)
self.repeatCount = None
self.previousSelection = None # A hack for middle-button paste: set by masterClickHandler, used by pasteText.
self.state = g.bunch(kind=None,n=None,handler=None)
#@-node:ekr.20061031131434.78:<< define externally visible ivars >>
#@+node:ekr.20061031131434.127:simulateCommand
def simulateCommand (self,commandName):

    k = self ; c = k.c

    commandName = commandName.strip()
    if not commandName: return


    aList = commandName.split(None)
    if len(aList) == 1:
        k.givenArgs = []
    else:
        commandName = aList[0]
        k.givenArgs = aList[1:]

    # g.trace(commandName,k.givenArgs)
    func = c.commandsDict.get(commandName)

    if func:
        # g.trace(commandName,func.__name__)
        stroke = None
        if commandName.startswith('specialCallback'):
            event = None # A legacy function.
        else: # Create a dummy event as a signal.
            event = g.bunch(c=c,keysym='',char='',widget=None)
        k.masterCommand(event,func,stroke)
        return k.funcReturn
    else:
        g.trace('no command for %s' % (commandName),color='red')
        if g.app.unitTesting:
            raise AttributeError
        else:
            return None
#@-node:ekr.20061031131434.127:simulateCommand
#@-node:ekr.20080229073116:Allow arguments to minibuffer commands
#@+node:ekr.20070625091423:Added translation services
@nocolor


- g.translateString does the actual translation.

- g.es and g.es_print now support exactly the same arguments.

  The methods translate only the first, third, etc arguments.
  Keyword arguments, color & newline, etc. are never translated.

@color
#@nonl
#@+node:ekr.20031218072017.3145:Most common functions...
# These are guaranteed always to exist for scripts.
#@+node:ekr.20031218072017.3147:choose
def choose(cond, a, b): # warning: evaluates all arguments

    if cond: return a
    else: return b
#@-node:ekr.20031218072017.3147:choose
#@+node:ekr.20031218072017.1474:enl, ecnl & ecnls
def ecnl(tabName='Log'):
    g.ecnls(1,tabName)

def ecnls(n,tabName='Log'):
    log = app.log
    if log and not log.isNull:
        while log.newlines < n:
            g.enl(tabName)

def enl(tabName='Log'):
    log = app.log
    if log and not log.isNull:
        log.newlines += 1
        log.putnl(tabName)
#@-node:ekr.20031218072017.1474:enl, ecnl & ecnls
#@+node:ekr.20070626132332:es & minitest
def es(s,*args,**keys):

    '''Put all non-keyword args to the log pane.
    The first, third, fifth, etc. arg translated by g.translateString.
    Supports color, comma, newline, spaces and tabName keyword arguments.
    '''
    # print 'es','app.log',repr(app.log),'log.isNull',not app.log or app.log.isNull,repr(s)
    # print 'es',repr(s)
    log = app.log
    if app.killed:
        return

    # Important: defining keyword arguments in addition to *args **does not work**.
    # See Section 5.3.4 (Calls) of the Python reference manual.
    # In other words, the following is about the best that can be done.
    color = keys.get('color')
    commas = keys.get('commas') ; commas = g.choose(commas=='True',True,False) # default is False
    newline = keys.get('newline') ; newline = g.choose(newline=='False',False,True) # default is True
    spaces= keys.get('spaces') ; spaces = g.choose(spaces=='False',False,True) # default is True
    tabName = keys.get('tabName','Log')

        # Default goes to log pane *not* the presently active pane.
    if color == 'suppress': return # New in 4.3.
    if type(s) != type("") and type(s) != type(u""):
        s = repr(s)
    s = g.translateArgs(s,args,commas,spaces)

    if app.batchMode:
        if app.log:
            app.log.put(s)
    elif g.unitTesting:
        if log and not log.isNull:
            s = g.toEncodedString(s,'ascii')
            if newline: print s
            else: print s,
    else:
        if log and log.isNull:
            pass
        elif log:
            log.put(s,color=color,tabName=tabName)
            for ch in s:
                if ch == '\n': log.newlines += 1
                else: log.newlines = 0
            if newline:
                g.ecnl(tabName=tabName) # only valid here
        elif newline:
            app.logWaiting.append((s+'\n',color),)
        else:
            app.logWaiting.append((s,color),)
#@+node:ekr.20071024101611:mini test of es
@nocolor
@first
@first
@

This doesn't work as an external unit test.
To test, select all following lines and do execute-script.

s1 = 'line1 Ä, ڱ,  궯, 奠 end'
s2 = g.toUnicode(s1,'utf-8')

for s in (s1,s2):
    g.es(s)
    g.es_print(s)
#@-node:ekr.20071024101611:mini test of es
#@-node:ekr.20070626132332:es & minitest
#@+node:ekr.20050707064040:es_print
# see: http://www.diveintopython.org/xml_processing/unicode.html

def es_print(s,*args,**keys):

    '''Print all non-keyword args, and put them to the log pane.
    The first, third, fifth, etc. arg translated by g.translateString.
    Supports color, comma, newline, spaces and tabName keyword arguments.
    '''

    encoding = sys.getdefaultencoding()

    # Important: defining keyword arguments in addition to *args **does not work**.
    # See Section 5.3.4 (Calls) of the Python reference manual.
    # In other words, the following is about the best that can be done.
    commas = keys.get('commas') ; commas  = g.choose(commas=='True',True,False) # default is False
    newline = keys.get('newline') ; newline = g.choose(newline=='False',False,True) # default is True
    spaces= keys.get('spaces') ; spaces  = g.choose(spaces=='False',False,True) # default is True

    try:
        if type(s) != type(u''):
            s = unicode(s,encoding)
    except Exception:
        s = g.toEncodedString(s,'ascii')

    s2 = g.translateArgs(s,args,commas,spaces)

    if newline:
        try:
            print s2
        except Exception:
            print g.toEncodedString(s2,'ascii')
    else:
        try:
            print s2,
        except Exception:
            print g.toEncodedString(s2,'ascii'),

    if g.app.gui and not g.app.gui.isNullGui and not g.unitTesting:
        g.es(s,*args,**keys)
#@+node:ekr.20070621092938:@@test g.es_print
if g.unitTesting:
    g.es_print('\ntest of es_print: Ă',color='red',newline=False)
    g.es_print('after')
    g.es_print('done')
#@-node:ekr.20070621092938:@@test g.es_print
#@-node:ekr.20050707064040:es_print
#@+node:ekr.20050707065530:es_trace
def es_trace(s,*args,**keys):

    g.trace(g.toEncodedString(s,'ascii'))
    g.es(s,*args,**keys)
#@-node:ekr.20050707065530:es_trace
#@+node:ekr.20080220111323:translateArgs
def translateArgs (s,args,commas,spaces):

    '''Return the concatenation of s and all args,

    with odd args translated.'''

    # Print the translated strings, but retain s for the later call to g.es.
    result = []
    if s:
        result.append(g.translateString(s))
    n = 1
    for arg in args:
        n += 1
        if type(arg) != type("") and type(arg) != type(u""):
            arg = repr(arg)
        elif (n % 2) == 1:
            arg = g.translateString(arg)
        if arg:
            if result:
                # if commas: result.append(',')
                if spaces: result.append(' ')
            result.append(arg)

    return ''.join(result)
#@-node:ekr.20080220111323:translateArgs
#@+node:ekr.20060810095921:translateString & tr
def translateString (s):

    '''Return the translated text of s.'''

    if g.app.translateToUpperCase:
        return s.upper()
    else:
        return gettext.gettext(s)

tr = translateString
#@nonl
#@-node:ekr.20060810095921:translateString & tr
#@+node:ekr.20031218072017.3148:top
if 0: # An extremely dangerous function.

    def top():

        """Return the commander of the topmost window"""

        # Warning: may be called during startup or shutdown when nothing exists.
        try:
            return app.log.c
        except Exception:
            return None
#@-node:ekr.20031218072017.3148:top
#@+node:ekr.20031218072017.3149:trace is defined below
#@-node:ekr.20031218072017.3149:trace is defined below
#@+node:ekr.20031218072017.3150:windows
def windows():
    return app.windowList
#@-node:ekr.20031218072017.3150:windows
#@-node:ekr.20031218072017.3145:Most common functions...
#@+node:ekr.20080220082727:@scan_g.es_results
@first # -*- coding: utf-8 -*-

@ To be translated...

#if and #else parts have different braces:
%s dir:
(in
)
*** Two exclusive handlers for
***Updating:
...
:
= requires @root in the headline
@comment disables untangle for
Indentation error in
Leo Log Window...
ParserError in
Referenced from
This is for testing if g.es blocks in a thread
Token error in
TokenError in
\nauto-completer scan complete
\ntest of es_print: Ă
adding
after
all plugin handlers...
all tests enabled: this may take awhile
already loaded
auto-saving outline
bad
bad @+leo sentinel
bad @+node sentinel
bad abbrev:
bad encoding in derived file:
bad open/close_flash_brackets setting: using defaults
bad tnode index:
blanks converted to tabs in
buffers...
c.target_language:
can not add
can not create
can not create temp file
can not create: read only:
can not execute
can not import
can not import Image module from PIL
can not import ImageTk module
can not import gc module
can not load enabled plugin:
can not load image:
can not open
can not open Aspell
can not open dictionary file:
can not open local dictionary
can not open script file:
can not open:
can not write %s
can't happen: is_sentinel
can't move node out of
changed:
check complete
check-derived-file passed
check-leo-file failed:
check-leo-file passed
checking Python code
clearing undo
collapse_nodes_during_finds
command is not valid in batch mode
command not ready yet
conflicting @header and @noheader directives
correcting hidden node: t=
correcting line endings in:
count:
created
created chapter
created directory:
created in
created:
creating menu from
creating new window
creating:
current directory:
debugger does not exist:
default tangle directory not found:
deleting tnode list for
directory
directory:
disabling save commands
done
dubious brackets in
dummy created
empty
enabled brief gc stats
enabled plugins...
enabled verbose gc stats
end of script
error handling:
error parsing
error pretty-printing
error reading:
error:
errors
errors inhibited read @auto
event
exception binding
exception creating directory:
exception creating temp file
exception creating:
exception deleting backup file:
exception deleting:
exception executing
exception executing command
exception executing script
exception executing:
exception handling
exception in
exception in g.importFromPath
exception in os.chmod
exception loading plugin
exception opening:
exception removing:
exception renaming
exception writing:
extend mode
file not found
file not found:
finished
first mismatched line at line
from
g.app.config: bad encoding:
g.init_zodb: can not import ZODB
g.init_zodb: exception creating ZODB.DB instance
generated line:
get_focus:
gui does not support a stand-alone find dialog
gui does not support the compare window
handlers for
hasFocusWidget:
head_lines:
ignoring
ignoring 3.x sentinel:
ignoring bad @comment directive:
ignoring bad @comment sentinel:
ignoring bad @language directive:
ignoring bad @language sentinel:
ignoring bad unknownAttributes key
ignoring command: already executing a command.
ignoring invalid key binding:
ignoring non-dictionary unknownAttributes for
ignoring non-pickleable attribute
ignoring non-string attribute
ignoring redundant -noref in:
ignoring redundant -nosent in:
ignoring:
ignoring: @comment
imported
in
in file:
indentation error in
info string
inhibits untangle for
ins:
inserting @ignore
instances
invalid @+leo sentinel in
invalid @encoding:
invalid @lineending directive:
invalid Paste As Clone
invalid encoding in .leo file:
is a sentinel line
is also a prefix of
is bound to:
is not
is not in the outline
len
leoID=
line
line:
lines
load dir:
loadOnePlugin: failed to load module
loaded plugin:
looking for a parent to tangle...
lossage...
may be read-only or in use
minibuffer hidden
missing lines
missing vnode:
modes conflict in:
newTnode: unexpected index type:
no @auto nodes in the selected tree
no @file node in the selected tree
no @file nodes in the selected tree
no @test or @suite nodes in selected outline
no Find script node
no ancestor @file node: using script line numbers
no bindings
no child index for
no children and less than 10 characters (excluding directives)
no debugger found.
no dirty @auto nodes in the selected tree
no dirty @file nodes
no docstring for
no file name
no matching #endif:
no more clones
no more misspellings
no previous command
no script selected
no state function for
no such command:
no text selected
no tnode with index:
nodes
nodes checked
not a clone:
not a valid MORE file
not changed.
not found
not found in
not found:
not written:
nothing follows section name
offending line...
offending line:\n
only
or
original line:
over-riding setting:
parent node:
path does not exist:
plugin
probably an outline topology error.
psyco now logging to:
psyco now running
putCount
putDescendentUnknownAttributes can't happen 2
putDescendentUnknownAttributes: unexpected pickling exception
putUaHelper: unexpected pickling exception
read only
read only:
reading:
recreating:
redefining
redo
relative path in @path directive:
relative_path_base_directory:
rename failed: no file created!
reopening:
replacing
requestedFocusWidget:
restoring
resurrected node:
running
save commands disabled
saved:
scanGnx: unexpected index type:
scanning for auto-completer...
seems to be mixed HTML and PHP:
selected text should contain one or more section names
selected text should start with a section name
skipping settings in
surprise in checkPythonNode
swap-words command not ready yet
syntax error in class node: can not continue
syntax error in:
syntax error: deleting
tabs converted to blanks in
tail_lines:
tangle complete
tangling parent
tangling...
the
the clipboard is not valid
the current node is not a clone
the text will be discarded
there may be conflicting settings!
time.strftime not available on this platform
time/count:
time:
to
to dictionary
unchanged:
undo
unexpected exception converting hexlified string to string
unexpected exception in
unexpected exception in app.setLeoID
unexpected exception in c.openWith
unexpected exception in g.create_temp_file
unexpected exception in g.getScript
unexpected exception in g.importFromPath(%s)
unexpected exception parsing
unexpected exception writing
unit tests
unknown attributes for
unknown attributes for tnode
unknown command name:
unknown language: using Python comment delimiters
unknown option:
unmatched
untangle complete
untangling...
using
using -asis option in:
using -thin option in:
using a blank one instead
using empty text.
using os.getenv('USER'):
warning:
warning: conflicting values for
warning: ignoring
warning: possible duplicate definition of:
warning: updating changed text in
with
write the @file node or use the Import Derived File command
writing erroneous:
wrote:
you may want to delete ressurected nodes
@c
# Decls...
color='red'
newline=True
tabName='Log'
False = 'False'
aList = 'aList'
args = 'args'
at_DOT_outputFileName = 'at.outputFileName'
at_DOT_root_DOT_headString_PARENS_ = 'at.root.headString()'
attrDict_DOT_get_LP_key_RP_ = 'attrDict.get(key)'
b2_DOT_pane = 'b2.pane'
backupName = 'backupName'
badline = 'badline'
base = 'base'
bindStroke = 'bindStroke'
bindings = 'bindings'
bunch_DOT_kind = 'bunch.kind'
c_DOT_config_DOT_getBool_LP__SQ_collapse_nodes_during_finds_SQ__RP_ = 'c.config.getBool(\'collapse_nodes_during_finds\')'
c_DOT_disableCommandsMessage = 'c.disableCommandsMessage'
c_DOT_shortFileName_PARENS_ = 'c.shortFileName()'
c_DOT_tangle_directory = 'c.tangle_directory'
c_DOT_target_language = 'c.target_language'
c_DOT_widget_name_LP_c_DOT_get_focus_PARENS__RP_ = 'c.widget_name(c.get_focus())'
c_DOT_widget_name_LP_c_DOT_hasFocusWidget_RP_ = 'c.widget_name(c.hasFocusWidget)'
c_DOT_widget_name_LP_c_DOT_requestedFocusWidget_RP_ = 'c.widget_name(c.requestedFocusWidget)'
c_DOT_widget_name_LP_w_RP_ = 'c.widget_name(w)'
ch = 'ch'
command = 'command'
commandName = 'commandName'
computeProxyObject = 'computeProxyObject'
configDir = 'configDir'
count = 'count'
d_DOT_get_LP_ch_RP_ = 'd.get(ch)'
d2 = 'd2'
d3 = 'd3'
data = 'data'
debugger = 'debugger'
delim1 = 'delim1'
delim2 = 'delim2'
delim3 = 'delim3'
dictionaryFileName = 'dictionaryFileName'
dir2 = 'dir2'
doc = 'doc'
dst = 'dst'
e = 'e'
encoding = 'encoding'
encodingName = 'encodingName'
errors = 'errors'
eventName = 'eventName'
exctype_DOT___name__ = 'exctype.__name__'
fileName = 'fileName'
file_name = 'file_name'
filename = 'filename'
fn = 'fn'
g_DOT_angleBrackets_LP__DQ___DQ__RP_ = 'g.angleBrackets("*")'
g_DOT_app_DOT_globalOpenDir = 'g.app.globalOpenDir'
g_DOT_app_DOT_gui_DOT_getFullVersion_LP_c_RP_ = 'g.app.gui.getFullVersion(c)'
g_DOT_app_DOT_gui_DOT_guiName_PARENS_ = 'g.app.gui.guiName()'
g_DOT_app_DOT_leoID = 'g.app.leoID'
g_DOT_choose_LP_val_COMMA__SQ_on_SQ__COMMA__SQ_off_SQ__RP_ = 'g.choose(val,\'on\',\'off\')'
g_DOT_get_line_LP_s_COMMA_i_RP_ = 'g.get_line(s,i)'
g_DOT_plugin_date_LP_m_RP_ = 'g.plugin_date(m)'
g_DOT_shortFileName_LP_fileName_RP_ = 'g.shortFileName(fileName)'
g_DOT_shortFileName_LP_path_RP_ = 'g.shortFileName(path)'
h = 'h'
head_lines = 'head_lines'
headline = 'headline'
homeDir = 'homeDir'
i = 'i'
index = 'index'
inputFileName = 'inputFileName'
ins = 'ins'
ivar = 'ivar'
k_DOT_prettyPrintKey_LP_stroke_RP_ = 'k.prettyPrintKey(stroke)'
k_DOT_state_DOT_kind = 'k.state.kind'
key = 'key'
keys = 'keys'
kind = 'kind'
len_LP_lines_RP_ = 'len(lines)'
len_LP_s_RP_ = 'len(s)'
letter = 'letter'
line = 'line'
line1 = 'line1'
line2 = 'line2'
loadDir = 'loadDir'
lp = 'lp'
m = 'm'
m_DOT___name__ = 'm.__name__'
m_DOT___version__ = 'm.__version__'
message = 'message'
min_LP_12_COMMA_n2_RP_ = 'min(12,n2)'
min_LP_20_COMMA_n1_RP_ = 'min(20,n1)'
modeName = 'modeName'
moduleName = 'moduleName'
msg = 'msg'
n = 'n'
n1 = 'n1'
n2 = 'n2'
n3 = 'n3'
name = 'name'
newFileName = 'newFileName'
p_DOT_headString_PARENS_ = 'p.headString()'
p_DOT_parent_PARENS__DOT_headString_PARENS_ = 'p.parent().headString()'
pane = 'pane'
part_DOT_name = 'part.name'
path = 'path'
prefix = 'prefix'
ratio = 'ratio'
relative_path = 'relative_path'
repr_LP_at_DOT_t_RP_ = 'repr(at.t)'
repr_LP_ch_RP_ = 'repr(ch)'
repr_LP_data_RP_ = 'repr(data)'
repr_LP_g_DOT_app_DOT_leoID_RP_ = 'repr(g.app.leoID)'
repr_LP_line_RP_ = 'repr(line)'
repr_LP_theId_RP_ = 'repr(theId)'
repr_LP_val_RP_ = 'repr(val)'
requestedType = 'requestedType'
root_DOT_headString_PARENS_ = 'root.headString()'
rp = 'rp'
s = 's'
s_DOT_strip_PARENS_ = 's.strip()'
s1 = 's1'
s2 = 's2'
s3 = 's3'
section_DOT_name = 'section.name'
self_DOT_currentWord = 'self.currentWord'
self_DOT_fileName = 'self.fileName'
self_DOT_outputFileName = 'self.outputFileName'
self_DOT_print_mode = 'self.print_mode'
self_DOT_putCount = 'self.putCount'
self_DOT_shortFileName = 'self.shortFileName'
self_DOT_tabName = 'self.tabName'
self_DOT_targetFileName = 'self.targetFileName'
sep = 'sep'
setting = 'setting'
shortcut = 'shortcut'
signon = 'signon'
spaces = 'spaces'
sparseMove = 'sparseMove'
src = 'src'
start = 'start'
start_line = 'start_line'
str_LP_i_1_RP_ = 'str(i+1)'
str_LP_index_RP_ = 'str(index)'
str_LP_message_RP_ = 'str(message)'
str_LP_msg_RP_ = 'str(msg)'
str_LP_n_RP_ = 'str(n)'
str_LP_time_RP_ = 'str(time)'
stroke = 'stroke'
tag = 'tag'
tail_lines = 'tail_lines'
target = 'target'
theDir = 'theDir'
theFile = 'theFile'
time_DOT_clock_PARENS_ = 'time.clock()'
title = 'title'
torv = 'torv'
type_LP_index_RP_ = 'type(index)'
type_LP_s_RP_ = 'type(s)'
url = 'url'
v_DOT_headString_PARENS_ = 'v.headString()'
val = 'val'
value = 'value'
version = 'version'
vnodeName = 'vnodeName'
word = 'word'
z = 'z'
z_line = 'z_line'
z_opt = 'z_opt'
zipMark = 'zipMark'
g.app.translateToUpperCase=True
# ---- @thin leo.py 
g.es_print('','-------------------- @thin leo.py ',color='red')
# -- node run
g.es("disabling save commands",color="red")
# -- node createFrame (leo.py)
g.es("file not found:",fileName)
# -- node getBatchScript
g.es_print("can not open script file:",name, color="red")
# -- node reportDirectories
g.es("%s dir:" % (kind),theDir,color="blue")
# -- node startPsyco
g.es("psyco now logging to:",theFile,color="blue")
g.es("psyco now running",color="blue")
# ---- @thin leoApp.py
g.es_print('','-------------------- @thin leoApp.py',color='red')
# -- node < < return if we can set leoID from sys.leoID> >
# g.es_print("leoID=",g.app.leoID,spaces=False,color='red')
g.es_print("leoID=",g_DOT_app_DOT_leoID,spaces=False,color='red')
# -- node < < return if we can set leoID from "leoID.txt" > >
# g.es('leoID=',g.app.leoID,' (in ',theDir,')',spaces=False,color="red")
g.es('leoID=',g_DOT_app_DOT_leoID,' (in ',theDir,')',spaces=False,color="red")
g.es('empty ',tag,' (in ',theDir,')',spaces=False,color = "red")
g.es_print('unexpected exception in app.setLeoID',color='red')
# -- node < < return if we can set leoID from os.getenv('USER') > >
# g.es("using os.getenv('USER'):",repr(theId),color='red')
g.es("using os.getenv('USER'):",repr_LP_theId_RP_,color='red')
# -- node < < put up a dialog requiring a valid id > >
# g.es('leoID=',repr(g.app.leoID),spaces=False,color="blue")
g.es('leoID=',repr_LP_g_DOT_app_DOT_leoID_RP_,spaces=False,color="blue")
# -- node < < attempt to create leoID.txt > >
g.es_print('',tag,'created in',theDir,color='red')
g.es('can not create',tag,'in',theDir,color='red')
# -- node app.writeWaitingLog
g.es('',s,color=color,newline=0)
# ---- @thin leoAtFile.py
g.es_print('','-------------------- @thin leoAtFile.py',color='red')
# -- node checkDerivedFile (atFile)
g.es_print('check-derived-file passed',color='blue')
# -- node < < warn on read-only file > >
g.es("read only:",fn,color="red")
# -- node read
# g.es("reading:",root.headString())
g.es("reading:",root_DOT_headString_PARENS_)
# -- node < < advise user to delete all unvisited nodes > >
# g.es('resurrected node:',p.headString(),color='blue')
g.es('resurrected node:',p_DOT_headString_PARENS_,color='blue')
g.es('in file:',fileName,color='blue')
g.es('you may want to delete ressurected nodes')
# -- node readAll (atFile)
g.es("no @file nodes in the selected tree")
# -- node readOneAtAutoNode (atFile)
# g.es("reading:",p.headString())
g.es("reading:",p_DOT_headString_PARENS_)
g.es_print('errors inhibited read @auto',fileName,color='red')
# -- node createNthChild3
g.es("dummy created")
# -- node handleLinesFollowingSentinel
g.es("using",s)
# g.es('',len(lines), "lines",m)
g.es('',len_LP_lines_RP_, "lines",m)
# -- node findChild4
g.es("write the @file node or use the Import Derived File command")
# -- node < < indicate that the node has been changed > >
# g.es("warning: updating changed text in",at.root.headString(),color="blue")
g.es("warning: updating changed text in",at_DOT_root_DOT_headString_PARENS_,color="blue")
# -- node < < bump at.correctedLines and tell about the correction > >
# g.es("correcting hidden node: t=",repr(at.t),color="red")
g.es("correcting hidden node: t=",repr_LP_at_DOT_t_RP_,color="red")
# -- node ignoreOldSentinel
g.es("ignoring 3.x sentinel:",s.strip(),color="blue")
# -- node < < handle @language > >
g.es("ignoring bad @language sentinel:",line,color="red")
# -- node < < handle @comment > >
g.es("ignoring bad @comment sentinel:",line,color="red")
# -- node copyAllTempBodyStringsToTnodes
# g.es("changed:",p.headString(),color="blue")
g.es("changed:",p_DOT_headString_PARENS_,color="blue")
# -- node < < read optional encoding param > >
g.es_print("bad encoding in derived file:",encoding)
# -- node < < set dirty and orphan bits on error > >
# g.es("not written:",at.outputFileName)
g.es("not written:",at_DOT_outputFileName)
# -- node < < say the command is finished > >
g.es("finished")
g.es("no @file nodes in the selected tree")
g.es("no dirty @file nodes")
# -- node writeAtAutoNodesHelper
g.es("finished")
g.es("no dirty @auto nodes in the selected tree")
g.es("no @auto nodes in the selected tree")
# -- node writeOneAtAutoNode & helpers
# g.es("not written:",at.outputFileName)
g.es("not written:",at_DOT_outputFileName)
# g.es("not written:",at.outputFileName)
g.es("not written:",at_DOT_outputFileName)
# -- node shouldWriteAtAutoNode
# g.es_print(p.headString(),'not written:',color='red')
g.es_print(p_DOT_headString_PARENS_,'not written:',color='red')
g.es_print('no children and less than 10 characters (excluding directives)',color='red')
# -- node writeMissing
g.es("finished")
g.es("no @file node in the selected tree")
# -- node hasSectionName
g.es('dubious brackets in',line)
# -- node < < handle @language > >
g.es("ignoring bad @language directive:",line,color="blue")
# -- node < < handle @comment > >
g.es("ignoring bad @comment directive:",line,color="blue")
# -- node replaceTargetFileIfDifferent
# g.es('unchanged:',self.shortFileName)
g.es('unchanged:',self_DOT_shortFileName)
# g.es('wrote:    ',self.shortFileName)
g.es('wrote:    ',self_DOT_shortFileName)
# g.es('created:  ',self.targetFileName)
g.es('created:  ',self_DOT_targetFileName)
# -- node < < report if the files differ only in line endings > >
# g.es("correcting line endings in:",self.targetFileName,color="blue")
g.es("correcting line endings in:",self_DOT_targetFileName,color="blue")
# -- node warnAboutOrpanAndIgnoredNodes
# g.es("parent node:",p.parent().headString(),color="blue")
g.es("parent node:",p_DOT_parent_PARENS__DOT_headString_PARENS_,color="blue")
# -- node writeException
# g.es("exception writing:",self.targetFileName,color="red")
g.es("exception writing:",self_DOT_targetFileName,color="red")
# g.es("exception deleting:",self.outputFileName,color="red")
g.es("exception deleting:",self_DOT_outputFileName,color="red")
# -- node < < Test for @header and @noheader > >
g.es("conflicting @header and @noheader directives")
# -- node < < Set comment strings from delims > >
g.es("unknown language: using Python comment delimiters")
# g.es("c.target_language:",c.target_language)
g.es("c_DOT_target_language:",c_DOT_target_language)
g.es('','delim1,delim2,delim3:','',delim1,'',delim2,'',delim3)
# ---- @thin leoBridge.py
g.es_print('','-------------------- @thin leoBridge.py',color='red')
# -- node < < try to get leoID from sys.leoID> >
# g.es("leoID=",g.app.leoID,spaces=False,color='red')
g.es("leoID=",g_DOT_app_DOT_leoID,spaces=False,color='red')
# -- node < < try to get leoID from "leoID.txt" > >
# g.es('leoID=',g.app.leoID,' (in ',theDir,')',spaces=False,color="red")
g.es('leoID=',g_DOT_app_DOT_leoID,' (in ',theDir,')',spaces=False,color="red")
g.es('empty ',tag,' (in ',theDir,')',spaces=False,color = "red")
g.es('unexpected exception in app.setLeoID',color='red')
# -- node < < try to get leoID from os.getenv('USER') > >
# g.es_print("using os.getenv('USER'):",repr(theId),color='red')
g.es_print("using os.getenv('USER'):",repr_LP_theId_RP_,color='red')
# -- node reportDirectories
g.es('',kind,'directory','',':',theDir,color='blue')
# -- node createFrame (leoBridge)
g.es('file not found', fileName,'creating new window')
# ---- @thin leoChapters.py
g.es_print('','-------------------- @thin leoChapters.py',color='red')
# -- node cc.createChapterByName
g.es('created chapter',name,color='blue')
# -- node cc.error
g.es_print(s,color='red')
# ---- @thin leoColor.py
g.es_print('','-------------------- @thin leoColor.py',color='red')
# ---- @thin leoCommands.py
g.es_print('','-------------------- @thin leoCommands.py',color='red')
# -- node doCommand
# g.es(c.disableCommandsMessage,color='blue')
g.es(c_DOT_disableCommandsMessage,color='blue')
g.es('ignoring command: already executing a command.',color='red')
g.es("exception executing command")
# -- node c.signOnWithVersion
g.es("Leo Log Window...",color=color)
g.es(signon)
# g.es('',"python %s.%s.%s, %s\n%s" % (n1,n2,n3,g.app.gui.getFullVersion(c),version))
g.es('',"python %s.%s.%s, %s\n%s" % (n1,n2,n3,g_DOT_app_DOT_gui_DOT_getFullVersion_LP_c_RP_,version))
# -- node openWith and allies
g.es("unexpected exception in c.openWith")
# -- node < < create or recreate temp file as needed > >
# g.es("reopening:",g.shortFileName(path),color="blue")
g.es("reopening:",g_DOT_shortFileName_LP_path_RP_,color="blue")
# -- node < < execute a command to open path in external editor > >
g.es("exception executing:",command)
# -- node createOpenWithTempFile
# g.es("recreating:  ",g.shortFileName(path),color="red")
g.es("recreating:  ",g_DOT_shortFileName_LP_path_RP_,color="red")
# g.es("creating:  ",g.shortFileName(path),color="blue")
g.es("creating:  ",g_DOT_shortFileName_LP_path_RP_,color="blue")
# g.es("time: " + str(time))
g.es("time: " + str_LP_time_RP_)
g.es("exception creating temp file",color="red")
# -- node save (commands)
g.es("save commands disabled",color="purple")
# -- node saveAs
g.es("save commands disabled",color="purple")
# -- node saveTo
g.es("save commands disabled",color="purple")
# -- node readOutlineOnly
g.es("can not open:",fileName)
# -- node readFileIntoFile
g.es("can not open:",fileName)
# -- node writeFileFromNode
g.es_print('wrote:',fileName,color='blue')
g.es('can not write %s',fileName,color='red')
# -- node c.executeScript & helpers
g.es("end of script",color="purple",tabName=tabName)
g.es("no script selected",color="blue",tabName=tabName)
# -- node goToLineNumber & allies
# g.es("error handling:",root.headString())
g.es("error handling:",root_DOT_headString_PARENS_)
# -- node < < set root > >
g.es("no ancestor @file node: using script line numbers", color="blue")
# -- node < < read the file into lines > >
g.es("not found:",fileName)
# -- node < < 4.2: get node from gnx > >
g.es("not found:",vnodeName,color="red")
# -- node < < 4.x: scan for the node using tnodeList and n > >
# g.es_print("no child index for",root.headString(),color="red")
g.es_print("no child index for",root_DOT_headString_PARENS_,color="red")
# -- node < < set p to the first vnode whose tnode is tnodeList[tnodeIndex] or set ok = false > >
g.es_print(s, color="red")
g.es_print(s, color="red")
g.es_print(s, color = "red")
# -- node < < set p to the first node whose headline matches vnodeName > >
g.es_print(s, color="red")
# -- node < < 3.x: scan for the node with the given childIndex > >
g.es("not found:",vnodeName, color="red")
# -- node < < put the cursor on line n2 of the body text > >
# g.es('only',len(lines),'lines',color="blue")
g.es('only',len_LP_lines_RP_,'lines',color="blue")
# -- node convertLineToVnodeNameIndexLine
g.es("bad @+leo sentinel")
# -- node < < handle delim while scanning backward > >
# g.es("line",str(n),"is a sentinel line")
g.es("line",str_LP_n_RP_,"is a sentinel line")
# -- node < < set vnodeName and (childIndex or gnx) from s > >
g.es("bad @+node sentinel")
# -- node convertAllBlanks
g.es("blanks converted to tabs in",count,"nodes")
# -- node convertAllTabs
g.es("tabs converted to blanks in",count,"nodes")
# -- node extract (test)
g.es("nothing follows section name",color="blue")
# -- node extractSection
g.es("nothing follows section name",color="blue")
# -- node < < Set headline for extractSection > >
g.es("selected text should start with a section name",color="blue")
# -- node extractSectionNames
g.es("selected text should contain one or more section names",color="blue")
# -- node < < trace head_lines, ins, tail_lines > >
g.es_print("head_lines: ",head_lines)
g.es_print("ins: ",ins)
g.es_print("tail_lines: ",tail_lines)
# -- node c.findMatchingBracket, helper and test
# g.es("unmatched",repr(ch))
g.es("unmatched",repr_LP_ch_RP_)
# -- node getTime
g.es("time.strftime not available on this platform",color="blue")
# -- node addComments (test)
g.es('no text selected',color='blue')
# -- node deleteComments (test)
g.es('no text selected',color='blue')
g.es('',"'%s'" % (d2),"not found",color='blue')
g.es('',"'%s'" % (d3),"not found",color='blue')
# -- node showFindPanel
# g.es('the',g.app.gui.guiName(),
#             'gui does not support a stand-alone find dialog',color='blue')
g.es('the',g_DOT_app_DOT_gui_DOT_guiName_PARENS_,
            'gui does not support a stand-alone find dialog',color='blue')
# -- node notValidInBatchMode
g.es('the',commandName,"command is not valid in batch mode")
# -- node c.checkOutline
g.es("all tests enabled: this may take awhile",color="blue")
# -- node < < remove unused tnodeList > >
g.es_print(s,color="blue")
# -- node < < do full tests > >
g.es('','.',newline=False)
# -- node < < give test failed message > >
g.es_print(s,color="red")
# -- node < <print summary message > >
g.es_print('',count,'nodes checked',errors,'errors',color=color)
# -- node checkAllPythonCode
g.es("check complete",color="blue")
# -- node < < print dots > >
g.es('','.',newline=False)
# -- node checkPythonCode
g.es("checking Python code   ")
g.es("surprise in checkPythonNode")
g.es("check complete",color="blue")
# -- node < < print dots > >
g.es('','.',newline=False)
# -- node checkPythonNode
g.es_print(s,color="blue")
# -- node tabNannyNode
g.es("ParserError in",headline,color="blue")
# g.es('',str(msg))
g.es('',str_LP_msg_RP_)
g.es("TokenError in",headline,color="blue")
# g.es('',str(msg))
g.es('',str_LP_msg_RP_)
g.es("indentation error in",headline,"line",badline,color="blue")
g.es(message)
g.es("offending line:\n",line2)
# -- node prettyPrintNode
g.es("error pretty-printing",h,"not changed.",color="blue")
# -- node markChangedHeadlines
g.es("done",color="blue")
# -- node markChangedRoots
g.es("done",color="blue")
# -- node markClones
g.es('the current node is not a clone',color='blue')
# -- node cantMoveMessage
g.es("can't move node out of",kind,color="blue")
# -- node c.toggleSparseMove
g.es(tag,'=',sparseMove,color='blue')
# -- node goToNextClone
# g.es('not a clone:',p.headString(),color='blue')
g.es('not a clone:',p_DOT_headString_PARENS_,color='blue')
g.es("done",color="blue")
# -- node findNextClone
g.es('no more clones',color='blue')
# -- node goToNextDirtyHeadline
g.es("done",color="blue")
# -- node goToNextMarkedHeadline
g.es("done",color="blue")
# -- node openCompareWindow
# g.es('the',g.app.gui.guiName(),
#             'gui does not support the compare window',color='blue')
g.es('the',g_DOT_app_DOT_gui_DOT_guiName_PARENS_,
            'gui does not support the compare window',color='blue')
# -- node openLeoSettings and openMyLeoSettings
g.es('',name,"not found in",configDir)
g.es('',name,"not found in",configDir,"or",homeDir)
# -- node openLeoScripts
g.es('not found:',fileName)
# -- node leoDocumentation
g.es("not found:",name)
# -- node leoHome
g.es("not found:",url)
# -- node leoPlugins
g.es("not found:",name)
# -- node leoTutorial (version number)
g.es("not found:",url)
# -- node leoUsersGuide
g.es("not found:",url)
# -- node initEncoding
g.es("bad", "%s: %s" % (encodingName,encoding))
# ---- @thin leoConfig.py
g.es_print('','-------------------- @thin leoConfig.py',color='red')
# -- node error
g.es(s,color="blue")
# -- node doIfGui
g.es_print(s,color='blue')
# -- node doMenus & helper
# g.es_print('creating menu from',c.shortFileName(),color='blue')
g.es_print('creating menu from',c_DOT_shortFileName_PARENS_,color='blue')
# -- node set (parseBaseClass)
g.es("over-riding setting:",name,"from",path)
# -- node traverse (parserBaseClass)
# g.es_print('skipping settings in',p.headString(),color='blue')
g.es_print('skipping settings in',p_DOT_headString_PARENS_,color='blue')
# -- node initEncoding
g.es("g.app.config: bad encoding:","%s: %s" % (ivar,encoding))
# -- node getValFromDict
# g.es_print('warning: ignoring',bunch.kind,'',setting,'is not',requestedType,color='red')
g.es_print('warning: ignoring',bunch_DOT_kind,'',setting,'is not',requestedType,color='red')
g.es_print('there may be conflicting settings!',color='red')
# -- node createRecentFiles
g.es_print('created',fileName,color='red')
g.es_print('can not create',fileName,color='red')
# -- node writeRecentFilesFileHelper
g.es('unexpected exception writing',fileName,color='red')
# -- node g.app.config.printSettings & helper
g.es('','%s %s = %s' % (letter,key,val))
# ---- @thin leoEditCommands.py
g.es_print('','-------------------- @thin leoEditCommands.py',color='red')
# -- node dynamicCompletion
g.es('command not ready yet',color='blue')
# -- node dynamicExpansion
g.es('command not ready yet',color='blue')
# -- node listAbbrevs
g.es('','%s=%s' % (z,s))
# -- node readAbbreviations
g.es('can not open',fileName)
# -- node writeAbbreviations
g.es('can not create',fileName)
# -- node listBuffers & listBuffersAlphabetically
g.es('buffers...')
g.es('',name)
g.es('buffers...')
g.es('',name)
# -- node debug & helper
g.es("info string")
# -- node findDebugger
g.es('debugger does not exist:',debugger,color='blue')
g.es('no debugger found.')
# -- node enable/disableGcTrace
g.es('enabled verbose gc stats',color='blue')
g.es('enabled brief gc stats',color='blue')
# -- node printFocus
# g.es_print('      hasFocusWidget:',c.widget_name(c.hasFocusWidget))
g.es_print('      hasFocusWidget:',c_DOT_widget_name_LP_c_DOT_hasFocusWidget_RP_)
# g.es_print('requestedFocusWidget:',c.widget_name(c.requestedFocusWidget))
g.es_print('requestedFocusWidget:',c_DOT_widget_name_LP_c_DOT_requestedFocusWidget_RP_)
# g.es_print('           get_focus:',c.widget_name(c.get_focus()))
g.es_print('           get_focus:',c_DOT_widget_name_LP_c_DOT_get_focus_PARENS__RP_)
# -- node appendImageDictToList
g.es('can not load image:',path)
# -- node getImage
g.es('can not import Image module from PIL',color='blue')
g.es('can not import ImageTk module',color='blue')
# -- node initBracketMatcher
g.es_print('bad open/close_flash_brackets setting: using defaults')
# -- node viewLossage
g.es('lossage...')
# g.es('',stroke or d.get(ch) or ch or 'None')
g.es('',stroke or d_DOT_get_LP_ch_RP_ or ch or 'None')
# -- node clear/set/ToggleExtendMode
# g.es('extend mode',g.choose(val,'on','off'),color='red')
g.es('extend mode',g_DOT_choose_LP_val_COMMA__SQ_on_SQ__COMMA__SQ_off_SQ__RP_,color='red')
# -- node swapWords
g.es('swap-words command not ready yet',color='blue')
# -- node getReadableTextFile
g.es('can not open',fileName)
# -- node saveFile
g.es('can not create',fileName)
# -- node helpForMinibuffer
g.es_print('',s)
# -- node helpForCommand
g.es('','%s:%s\n%s\n' % (commandName,bindings,s),color='blue')
# -- node aproposAutocompletion
g.es_print('',s)
# -- node aproposBindings
g.es_print('',s)
# -- node aproposDebuggingCommands
g.es_print('',s)
# -- node aproposFindCommands
g.es_print('',s)
# -- node loadFile & helpers
g.es('can not open',fileName)
# -- node saveMacros & helper
g.es('can not create',fileName)
# -- node findNextMatch (query-replace)
g.es('command not ready yet',color='blue')
# -- node Find options wrappers
# g.es('collapse_nodes_during_finds',c.config.getBool('collapse_nodes_during_finds'))
g.es('collapse_nodes_during_finds',c_DOT_config_DOT_getBool_LP__SQ_collapse_nodes_during_finds_SQ__RP_)
# -- node scolorizer LATER
g.es('command not ready yet',color='blue')
# -- node init_aspell
g.es_print('can not open dictionary file:',dictionaryFileName, color='red')
g.es_print('can not open Aspell',color='red')
# -- node readDictionary
g.es("can not open local dictionary",fileName,"using a blank one instead")
# -- node add
g.es("adding ", color= "blue", newline= False)
# g.es('','%s' % self.currentWord)
g.es('','%s' % self_DOT_currentWord)
# g.es("can not add",self.currentWord,"to dictionary",color="red")
g.es("can not add",self_DOT_currentWord,"to dictionary",color="red")
# -- node find & helpers
g.es("no more misspellings")
# -- node hide
g.es(message,color='blue')
# -- node ignore
g.es("ignoring ",color= "blue", newline= False)
# g.es('','%s' % self.currentWord)
g.es('','%s' % self_DOT_currentWord)
# -- node report
g.es_print(message,color='blue')
# ---- @thin leoFileCommands.py
g.es_print('','-------------------- @thin leoFileCommands.py',color='red')
# -- node processingInstruction (stylesheet)
g.es('','%s: %s' % (target,data),color='blue')
# -- node startVnodes
# g.es("reading:",self.fileName)
g.es("reading:",self_DOT_fileName)
# -- node checkLeoFile (fileCommands)
g.es_print('check-leo-file passed',color='blue')
# g.es_print('check-leo-file failed:',str(message),color='red')
g.es_print('check-leo-file failed:',str_LP_message_RP_,color='red')
# -- node getLeoOutlineFromClipboard & helpers
g.es("invalid Paste As Clone",color="blue")
g.es("the clipboard is not valid ",color="blue")
# -- node < < warn on read-only files > >
g.es("read only:",fileName,color="red")
# -- node newTnode
# g.es("bad tnode index:",str(index),"using empty text.")
g.es("bad tnode index:",str_LP_index_RP_,"using empty text.")
# g.es("newTnode: unexpected index type:",type(index),index,color="red")
g.es("newTnode: unexpected index type:",type_LP_index_RP_,index,color="red")
# -- node getAllLeoElements
g.es("reading:",fileName)
# -- node getPrefs
# g.es("default tangle directory not found:",c.tangle_directory)
g.es("default tangle directory not found:",c_DOT_tangle_directory)
# -- node getTnode
# g.es("no tnode with index:",str(index),"the text will be discarded")
g.es("no tnode with index:",str_LP_index_RP_,"the text will be discarded")
# -- node < < handle unknown attributes > >
g.es_print("unknown attributes for tnode",color = "blue")
# g.es_print('',"%s = %s" % (key,attrDict.get(key)))
g.es_print('',"%s = %s" % (key,attrDict_DOT_get_LP_key_RP_))
# -- node < < handle unknown vnode attributes > >
# g.es_print("unknown attributes for",v.headString(),color="blue")
g.es_print("unknown attributes for",v_DOT_headString_PARENS_,color="blue")
# g.es_print('',"%s = %s" % (key,attrDict.get(key)))
g.es_print('',"%s = %s" % (key,attrDict_DOT_get_LP_key_RP_))
# -- node getExistingVnode
g.es("missing vnode:",headline,color="red")
g.es("probably an outline topology error.")
# -- node getXmlVersionTag
g.es("invalid encoding in .leo file:",encoding,color="red")
# -- node getSaxUa
g.es_print('unexpected exception converting hexlified string to string')
# -- node parse_leo_file
g.es_print('error parsing',inputFileName,color='red')
g.es_print('unexpected exception parsing',inputFileName,color='red')
# -- node save (fileCommands)
g.es("clearing undo")
# -- node putSavedMessage
# g.es("saved:","%s%s" % (zipMark,g.shortFileName(fileName)))
g.es("saved:","%s%s" % (zipMark,g_DOT_shortFileName_LP_fileName_RP_))
# -- node deleteFileWithMessage
g.es("read only",color="red")
g.es("exception deleting backup file:",fileName)
# -- node putUnknownAttributes & helper
g.es("ignoring non-dictionary unknownAttributes for",torv,color="blue")
# -- node putUaHelper
g.es("ignoring non-string attribute",key,"in",torv,color="blue")
g.es('putUaHelper: unexpected pickling exception',color='red')
g.es("ignoring non-pickleable attribute",key,"in",torv,color="blue")
# -- node < < Append tnodeList and unKnownAttributes to attrs> >
# g.es("deleting tnode list for",p.headString(),color="blue")
g.es("deleting tnode list for",p_DOT_headString_PARENS_,color="blue")
# -- node < < issue informational messages > >
# g.es("writing erroneous:",p.headString(),color="blue")
g.es("writing erroneous:",p_DOT_headString_PARENS_,color="blue")
# -- node putDescendentUnknownAttributes
g.es("ignoring non-dictionary unknownAttributes for",p,color="blue")
g.es("ignoring bad unknownAttributes key",key,"in",p,color="blue")
g.es('putDescendentUnknownAttributes: unexpected pickling exception',color='red')
g.es("putDescendentUnknownAttributes can't happen 2",color='red')
# -- node write_Leo_file
# g.es_print('len',len(s),'putCount',self.putCount)
g.es_print('len',len_LP_s_RP_,'putCount',self_DOT_putCount)
g.es("exception writing:",fileName)
# -- node < < return if the .leo file is read-only > >
g.es("can not create: read only:",fileName,color="red")
# -- node < < create backup file > >
g.es("read only",color="red")
# -- node < < rename backupName to fileName > >
g.es("restoring",fileName,"from",backupName)
# -- node writeAtFileNodes
g.es("auto-saving outline",color="blue")
# -- node writeDirtyAtFileNodes
g.es("auto-saving outline",color="blue")
# -- node writeMissingAtFileNodes
g.es("auto-saving outline",color="blue")
# -- node writeOutlineOnly
g.es('done',color='blue')
# ---- @thin leoGlobals.py
g.es_print('','-------------------- @thin leoGlobals.py',color='red')
# -- node computeLoadDir
g.es("load dir:",loadDir,color="blue")
# -- node set_language
# g.es("ignoring:",g.get_line(s,i))
g.es("ignoring:",g_DOT_get_line_LP_s_COMMA_i_RP_)
# -- node < < set theDict for @ directives > >
g.es("warning: conflicting values for",word,color="blue")
# -- node < < set theDict["root"] for noweb * chunks > >
# g.es('',g.angleBrackets("*") + "= requires @root in the headline")
g.es('',g_DOT_angleBrackets_LP__DQ___DQ__RP_ + "= requires @root in the headline")
# -- node g.scanAtEncodingDirective
g.es("invalid @encoding:",encoding,color="red")
# -- node g.scanAtLineendingDirective
g.es("invalid @lineending directive:",e,color="red")
# -- node g.scanAtPagewidthDirective
g.es("ignoring",s,color="red")
# -- node < < scan another @root option > >
# g.es("modes conflict in:",g.get_line(s,i))
g.es("modes conflict in:",g_DOT_get_line_LP_s_COMMA_i_RP_)
# g.es("modes conflict in:",g.get_line(s,i))
g.es("modes conflict in:",g_DOT_get_line_LP_s_COMMA_i_RP_)
g.es("unknown option:",z_opt,"in",z_line)
# -- node g.scanAtTabwidthDirective
g.es("ignoring",s,color="red")
# -- node alert
g.es('',message)
# -- node es_dump
g.es_print('',title)
g.es_print('',aList)
# -- node es_error
g.es(s,color=color)
# -- node es_event_exception
g.es("exception handling ",eventName,"event")
g.es('',i)
# -- node es_exception_type
# g.es_print('','%s, %s' % (exctype.__name__, value),color=color)
g.es_print('','%s, %s' % (exctype_DOT___name__, value),color=color)
# -- node getLastTracebackFileAndLineNumber
# g.es_print('',repr(val))
g.es_print('',repr_LP_val_RP_)
# g.es_print('',repr(data))
g.es_print('',repr_LP_data_RP_)
# -- node Timing
# g.es('',"%s %6.3f" % (message,(time.clock()-start)))
###g.es('',"%s %6.3f" % (message,(time_DOT_clock_PARENS_-start)))
# -- node g.create_temp_file
###g.es('unexpected exception in g.create_temp_file',color='red')
# -- node g.is_sentinel
g.es("can't happen: is_sentinel",color="red")
# -- node g.makeAllNonExistentDirectories
g.es("created directory:",path)
g.es("exception creating directory:",path)
# -- node g.openLeoOrZipFile
g.es("can not open:",fileName,color="blue")
# -- node g.setGlobalOpenDir
# g.es('current directory:',g.app.globalOpenDir)
g.es('current directory:',g_DOT_app_DOT_globalOpenDir)
# -- node g.update_file_if_changed
g.es('','%12s: %s' % (kind,file_name))
g.es("rename failed: no file created!",color="red")
g.es('',file_name," may be read-only or in use")
# -- node g.utils_remove
g.es("exception removing:",fileName)
# -- node g.utils_rename
g.es('exception renaming',src,'to',dst,color='red')
# -- node g.utils_chmod
g.es("exception in os.chmod",fileName)
# -- node enable_gc_debug
g.es('can not import gc module',color='blue')
# -- node g.doHook
g.es_print(s,color="blue")
# -- node g.plugin_signon
# g.es('',"...%s.py v%s: %s" % (
#             m.__name__, m.__version__, g.plugin_date(m)))
g.es('',"...%s.py v%s: %s" % (
            m_DOT___name__, m_DOT___version__, g_DOT_plugin_date_LP_m_RP_))
# -- node mini test of es
g.es(s)
g.es_print(s)
# -- node es_print
# -- node @@test g.es_print
g.es_print('\ntest of es_print: Ă',color='red',newline=False)
g.es_print('after')
g.es_print('done')
# -- node es_trace
# -- node < < scan another @file option > >
g.es("using -asis option in:",h)
g.es("ignoring redundant -noref in:",h)
g.es("ignoring redundant -nosent in:",h)
g.es("using -thin option in:",h)
g.es("unknown option:",z_opt,"in",h)
# -- node scanError
g.es('',s)
# -- node skip_pp_if
g.es("#if and #else parts have different braces:",start_line)
g.es("no matching #endif:",start_line)
# -- node g.initScriptFind (set up dialog)
g.es("no Find script node",color="red")
# -- node g.handleScriptException
g.es("exception executing script",color='blue')
# -- node < < dump the lines near the error > >
g.es_print('',s)
g.es('',s,newline=False)
# -- node reportBadChars
g.es(s2,color='red')
g.es(s2,color='red')
# -- node g.executeScript
g.es("exception executing",name,color="red")
# -- node g.getScript
g.es_print("unexpected exception in g.getScript")
# -- node g.cantImport
g.es_print('',s,color="blue")
# -- node g.importFromPath
g.es_print("exception in g.importFromPath",color='blue')
g.es_print("unexpected exception in g.importFromPath(%s)" %
                    (name),color='blue')
# -- node g.init_zodb
g.es('g.init_zodb: can not import ZODB')
g.es('g.init_zodb: exception creating ZODB.DB instance')
# ---- @thin leoImport.py
g.es_print('','-------------------- @thin leoImport.py',color='red')
# -- node exportHeadlines
g.es("can not open",fileName,color="blue")
# -- node flattenOutline
g.es("can not open",fileName,color="blue")
# -- node outlineToWeb
g.es("can not open",fileName,color="blue")
# -- node < < Read file into s > >
g.es("can not open",fileName, color="blue")
# -- node < < set delims from the header line > >
g.es("invalid @+leo sentinel in",fileName)
# -- node < < Write s into newFileName > >
g.es("created:",newFileName)
g.es("exception creating:",newFileName)
# -- node < < open filename to f, or return > >
g.es("exception opening:",filename)
# -- node error
g.es('',s)
# -- node < < Read file into s > >
g.es("can not open", "%s%s" % (z,fileName),color='red')
# -- node readAtAutoNodes (importCommands) & helper
# g.es_print('ignoring',p.headString(),color='blue')
g.es_print('ignoring',p_DOT_headString_PARENS_,color='blue')
g.es(message,color='blue')
# -- node importFilesCommand
g.es("imported",fileName,color="blue")
# -- node importFlattenedOutline
g.es("not a valid MORE file",fileName)
# -- node < < Read the file into array > >
g.es("can not open",fileName, color="blue")
# -- node scanWebFile (handles limbo)
g.es("can not import",fileName, color="blue")
# -- node cstLookup
g.es('',"****** %s" % (target),"is also a prefix of",s)
g.es("replacing",target,"with",s)
# -- node scanPHPText
g.es_print('seems to be mixed HTML and PHP:',fileName)
# -- node compareHelper
# g.es_print('first mismatched line at line',str(i+1))
g.es_print('first mismatched line at line',str_LP_i_1_RP_)
g.es_print('original line: ',line1)
g.es_print('generated line:',line2)
g.es_print('missing lines')
# g.es_print('',repr(line))
g.es_print('',repr_LP_line_RP_)
# -- node checkLeadingWhitespace
# g.es_print('line:',repr(line),color='red')
g.es_print('line:',repr_LP_line_RP_,color='red')
# -- node reportMismatch
# -- node insertIgnoreDirective
g.es_print('inserting @ignore',color='blue')
# -- node error, oops, report and warning
g.es_print('',s,color='red')
g.es_print('error:',s,color='red')
g.es_print('warning:',s,color='red')
# ---- @thin leoKeys.py
g.es_print('','-------------------- @thin leoKeys.py',color='red')
# -- node showAutocompleter/CalltipsStatus
g.es(s,color='red')
g.es(s,color='red')
# -- node computeCompletionList
# g.es('',z,tabName=self.tabName)
g.es('',z,tabName=self_DOT_tabName)
# -- node info
g.es('no docstring for',word,color='blue')
g.es('',doc,tabName='Info')
g.es('no docstring for',word,color='blue')
# -- node scan
g.es("This is for testing if g.es blocks in a thread", color = 'pink' )
# -- node scanOutline
g.es_print('scanning for auto-completer...')
g.es('','.',newline=False)
g.es_print('\nauto-completer scan complete',color='blue')
# -- node createClassObjectFromString
g.es_print('unexpected exception in',computeProxyObject)
# -- node forgivingParser
g.es_print('syntax error in class node: can not continue')
# g.es_print('syntax error: deleting',p.headString())
g.es_print('syntax error: deleting',p_DOT_headString_PARENS_)
# -- node bindKey
g.es_print('exception binding',shortcut,'to',commandName)
# -- node < < give warning and return if we try to bind to Enter or Leave > >
g.es_print('ignoring invalid key binding:','%s = %s' % (
                commandName,shortcut),color='blue')
# -- node < < remove previous conflicting definitions from bunchList > >
# g.es_print('redefining',z,'in',b2.pane,'to',commandName,'in',pane,color='red')
g.es_print('redefining',z,'in',b2_DOT_pane,'to',commandName,'in',pane,color='red')
# -- node k.initAbbrev
g.es_print('bad abbrev:',key,'unknown command name:',commandName,color='blue')
# -- node k.makeMasterGuiBinding
# g.es_print('exception binding',bindStroke,'to',c.widget_name(w),color='blue')
g.es_print('exception binding',bindStroke,'to',c_DOT_widget_name_LP_w_RP_,color='blue')
# -- node callStateFunction
# g.es_print('no state function for',k.state.kind,color='red')
g.es_print('no state function for',k_DOT_state_DOT_kind,color='red')
# -- node k.show/hide/toggleMinibuffer
g.es('minibuffer hidden',color='red')
g.es('',commandName,'is bound to:',shortcut)
# -- node printBindings & helper
g.es('no bindings')
g.es('','%s %s' % (sep, prefix),tabName=tabName)
g.es('','%s %s' % (sep, 'Plain Keys',),tabName=tabName)
# -- node printBindingsHelper
# g.es('','%*s %*s %s' % (-n1,s1,-(min(12,n2)),s2,s3),tabName='Bindings')
# g.es('','%*s %*s %s' % (-n1,s1,-(min_LP_12_COMMA_n2_RP_),s2,s3),tabName='Bindings')
# -- node printCommands
# g.es('','%*s %*s %s' % (-n1,s1,-(min(12,n2)),s2,s3),tabName=tabName)
# g.es('','%*s %*s %s' % (-n1,s1,-(min_LP_12_COMMA_n2_RP_),s2,s3),tabName=tabName)
# -- node repeatComplexCommand & helper
g.es('no previous command',color='blue')
# -- node k.registerCommand
g.es_print('redefining',commandName, color='red')
# g.es_print('','@command: %s = %s' % (
#                 commandName,k.prettyPrintKey(stroke)),color='blue')
g.es_print('','@command: %s = %s' % (
                commandName,k_DOT_prettyPrintKey_LP_stroke_RP_),color='blue')
g.es_print('','@command: %s' % (commandName),color='blue')
# -- node createModeBindings
g.es_print('no such command:',commandName,'Referenced from',modeName)
# -- node modeHelpHelper
g.es('','%s mode\n\n' % modeName,tabName=tabName)
###g.es('','%*s %s' % (n,s1,s2),tabName=tabName)
# -- node k.computeCompletionList
# g.es('','%*s %*s %s' % (-(min(20,n1)),s1,n2,s2,s3),tabName=tabName)
# g.es('','%*s %*s %s' % (-(min_LP_20_COMMA_n1_RP_),s1,n2,s2,s3),tabName=tabName)
# -- node k.showFileNameTabList
g.es('',s,tabName=tabName)
# ---- @thin leoNodes.py
g.es_print('','-------------------- @thin leoNodes.py',color='red')
# -- node v.bodyString
g.es_print('',s,color="red")
# -- node v.headString & v.cleanHeadString
g.es_print('',s,color="red")
# -- node scanGnx
# g.es("scanGnx: unexpected index type:",type(s),'',s,color="red")
g.es("scanGnx: unexpected index type:",type_LP_s_RP_,'',s,color="red")
# ---- @thin leoPlugins.py
g.es_print('','-------------------- @thin leoPlugins.py',color='red')
# -- node loadHandlers & helper
# -- node loadOnePlugin
g.es_print('plugin',moduleName,'already loaded',color="blue")
g.es_print('loadOnePlugin: failed to load module',moduleName,color="red")
g.es('exception loading plugin',color='red')
g.es_print('can not load enabled plugin:',moduleName,color="red")
g.es_print('loaded plugin:',moduleName,color="blue")
# -- node printHandlers
g.es_print('handlers for',moduleName,'...')
g.es_print('all plugin handlers...')
g.es_print('','%25s %s' % (tag,key))
# -- node printPlugins
g.es_print('enabled plugins...')
g.es_print('',key)
# -- node registerExclusiveHandler
g.es("*** Two exclusive handlers for","'%s'" % (tag))
# ---- @thin leoPymacs.py
g.es_print('','-------------------- @thin leoPymacs.py',color='red')
# -- node open
g.es_print('','leoPymacs.open:','no file name')
g.es_print('','leoPymacs.open:',c)
g.es_print('','leoPymacs.open:','can not open',fileName)
# ---- @thin leoTangle.py
g.es_print('','-------------------- @thin leoTangle.py',color='red')
# -- node < < call tangle_done.run() or untangle_done.run() > >
g.es("can not execute","tangle_done.run()")
g.es("can not execute","tangle_done.run()")
# -- node initTangleCommand
g.es("tangling...")
# -- node initUntangleCommand
g.es("untangling...")
# -- node tangle
g.es("looking for a parent to tangle...")
g.es("tangling parent")
g.es("tangle complete")
# -- node tangleAll
g.es("tangle complete")
# -- node tangleMarked
g.es("tangle complete")
# -- node untangle
g.es("untangle complete")
# -- node untangleAll
g.es("untangle complete")
# -- node untangleMarked
g.es("untangle complete")
# -- node untangleRoot (calls cleanup)
g.es('','@root ' + path)
# -- node < < return if @silent or unknown language > >
g.es("@comment disables untangle for",path, color="blue")
# g.es('','@%s' % (self.print_mode),"inhibits untangle for",path, color="blue")
g.es('','@%s' % (self_DOT_print_mode),"inhibits untangle for",path, color="blue")
# -- node < < Read the file into file_buf  > > in untangleRoot
g.es("error reading:",path)
# -- node tangle.put_all_roots
g.es("can not create temp file")
g.es("unchanged:",file_name)
# -- node st_check
# g.es('',' ' * 4,'warning:',lp,'',section.name,'',rp,'has been defined but not used.')
g.es('',' ' * 4,'warning:',lp,'',section_DOT_name,'',rp,'has been defined but not used.')
# -- node < <check for duplicate code definitions > >
g.es('warning: possible duplicate definition of:',s)
# -- node ust_warn_about_orphans
# g.es("warning:",'%s%s%s' % (lp,part.name,rp),"is not in the outline")
g.es("warning:",'%s%s%s' % (lp,part_DOT_name,rp),"is not in the outline")
# -- node update_def (pass 2)
# g.es("***Updating:",p.headString())
g.es("***Updating:",p_DOT_headString_PARENS_)
# -- node < < Test for @comment and @language > >
g.es("ignoring: @comment",z)
# -- node < < handle absolute @path > >
g.es("relative_path_base_directory:",base)
g.es("relative path in @path directive:",relative_path)
# -- node < < Test for @header and @noheader > >
g.es("conflicting @header and @noheader directives")
# -- node < < handle absolute path > >
g.es("relative_path_base_directory:",base)
g.es('',kind,"directory:",dir2)
# ---- @thin leoTest.py
g.es_print('','-------------------- @thin leoTest.py',color='red')
# -- node runTimerOnNode
# g.es_print("count:",count,"time/count:",ratio,'',p.headString())
g.es_print("count:",count,"time/count:",ratio,'',p_DOT_headString_PARENS_)
# -- node runTests
g.es('running',kind,'unit tests',color='blue')
g.es_print('no @test or @suite nodes in selected outline')
# -- node checkFileSyntax
g.es("syntax error in:",fileName,color="blue")
# -- node checkFileTabs
g.es_print("Token error in",fileName,color="blue")
g.es_print('',msg)
g.es_print("Indentation error in",fileName,"line",badline,color="blue")
g.es_print('',message)
g.es_print("offending line...")
g.es_print('',line)
# -- node importAllModulesInPath
g.es("path does not exist:",path)
# ---- @thin leoUndo.py
g.es_print('','-------------------- @thin leoUndo.py',color='red')
# -- node < < set newBead if we can't share the previous bead > >
g.es('exception in','setUndoRedoTypingParams',color='blue')
# -- node redoGroup
g.es("redo",count,"instances")
# -- node undoGroup
g.es("undo",count,"instances")
g.app.translateToUpperCase=False
#@nonl
#@-node:ekr.20080220082727:@scan_g.es_results
#@-node:ekr.20070625091423:Added translation services
#@+node:ekr.20071217032247:Compile regexp's once in findNextMatch
@nocolor

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

The regexHelper method does compile regexp's before searching for them.  Alas, this method is called once per searched node, which greatly increases the compilation overhead.  Presumably it would be much better to compile the regexp once, say in findNextMatch.

@color
#@nonl
#@+node:ekr.20031218072017.3075:findNextMatch
# Resumes the search where it left off.
# The caller must call set_first_incremental_search or set_first_batch_search.

def findNextMatch(self):

    c = self.c ; trace = self.trace

    if trace: g.trace('entry',g.callers())

    if not self.search_headline and not self.search_body:
        if trace: g.trace('nothing to search')
        return None, None

    if len(self.find_text) == 0:
        if trace: g.trace('no find text')
        return None, None

    p = self.p ; self.errors = 0
    attempts = 0
    self.backwardAttempts = 0

    # New in Leo 4.4.8: precompute the regexp for regexHelper.
    if self.pattern_match:
        try: # Precompile the regexp.
            flags = re.MULTILINE
            if self.ignore_case: flags |= re.IGNORECASE
            self.re_obj = re.compile(self.find_text,flags)
        except Exception:
            g.es('invalid regular expression:',pattern,color='blue')
            self.errors += 1 # Abort the search.
            return None,None

    while p:
        pos, newpos = self.search()
        if trace: g.trace('attempt','pos',pos,'p',p.headString())
        if pos is not None:
            if self.mark_finds:
                p.setMarked()
                c.frame.tree.drawIcon(p) # redraw only the icon.
            if trace: g.trace('success',pos,newpos)
            return pos, newpos
        elif self.errors:
            g.trace('find errors')
            return None,None # Abort the search.
        elif self.node_only:
            if trace: g.trace('fail: node only')
            return None,None # We are only searching one node.
        else:
            if trace: g.trace('failed attempt',p)
            attempts += 1
            p = self.p = self.selectNextPosition()

    if trace: g.trace('attempts',attempts,'backwardAttempts',self.backwardAttempts)
    return None, None
#@-node:ekr.20031218072017.3075:findNextMatch
#@+node:ekr.20060526092203:regexHelper
def regexHelper (self,s,i,j,pattern,backwards,nocase):

    re_obj = self.re_obj # Use the pre-compiled object
    if not re_obj:
        g.trace('can not happen: no re_obj')
        return -1,-1

    # try:
        # flags = re.MULTILINE
        # if nocase: flags |= re.IGNORECASE
        # re_obj = re.compile(pattern,flags)
    # except Exception:
        # g.es('invalid regular expression:',pattern,color='blue')
        # self.errors += 1 # Abort the search.
        # return -1, -1

    if backwards: # Scan to the last match.  We must use search here.
        last_mo = None ; i = 0
        while i < len(s):
            mo = re_obj.search(s,i,j)
            if not mo: break
            i += 1 ; last_mo = mo
        mo = last_mo
    else:
        mo = re_obj.search(s,i,j)

    if 0:
        g.trace('i',i,'j',j,'s[i:j]',repr(s[i:j]),
            'mo.start',mo and mo.start(),'mo.end',mo and mo.end())

    while mo and 0 <= i < len(s):
        if mo.start() == mo.end():
            if backwards:
                # Search backward using match instead of search.
                i -= 1
                while 0 <= i < len(s):
                    mo = re_obj.match(s,i,j)
                    if mo: break
                    i -= 1
            else:
                i += 1 ; mo = re_obj.search(s,i,j)
        else:
            self.match_obj = mo
            return mo.start(),mo.end()
    self.match_obj = None
    return -1,-1
#@-node:ekr.20060526092203:regexHelper
#@+node:ekr.20031218072017.3077:search & helpers
def search (self):

    """Search s_ctrl for self.find_text under the control of the
    whole_word, ignore_case, and pattern_match ivars.

    Returns (pos, newpos) or (None,None)."""

    c = self.c ; p = self.p ; w = self.s_ctrl ; trace = self.trace
    index = w.getInsertPoint()
    s = w.getAllText()

    # g.trace(index,repr(s[index:index+20]))
    stopindex = g.choose(self.reverse,0,len(s)) # 'end' doesn't work here.
    pos,newpos = self.searchHelper(s,index,stopindex,self.find_text,
        backwards=self.reverse,nocase=self.ignore_case,
        regexp=self.pattern_match,word=self.whole_word)

    # g.trace('pos,newpos',pos,newpos)
    if pos == -1:
        if trace: g.trace('** pos is -1',pos,newpos)
        return None,None
    << fail if we are passed the wrap point >>
    insert = g.choose(self.reverse,min(pos,newpos),max(pos,newpos))
    w.setSelectionRange(pos,newpos,insert=insert)

    if trace: g.trace('** returns',pos,newpos)
    return pos,newpos
#@+node:ekr.20060526140328:<< fail if we are passed the wrap point >>
if self.wrapping and self.wrapPos is not None and self.wrapPosition and p == self.wrapPosition:

    if self.reverse and pos < self.wrapPos:
        if trace: g.trace("** reverse wrap done",pos,newpos)
        return None, None

    if not self.reverse and newpos > self.wrapPos:
        if trace: g.trace('** wrap done',pos,newpos)
        return None, None
#@-node:ekr.20060526140328:<< fail if we are passed the wrap point >>
#@+node:ekr.20060526081931:searchHelper & allies
def searchHelper (self,s,i,j,pattern,backwards,nocase,regexp,word,swapij=True):

    trace = self.trace

    if swapij and backwards: i,j = j,i

    if trace: g.trace('back,nocase,regexp,word,',
        backwards,nocase,regexp,word,i,j,repr(s[i:i+20]))

    if not s[i:j] or not pattern:
        if trace: g.trace('empty',i,j,'len(s)',len(s),'pattern',pattern)
        return -1,-1

    if regexp:
        pos,newpos = self.regexHelper(s,i,j,pattern,backwards,nocase)
    elif backwards:
        pos,newpos = self.backwardsHelper(s,i,j,pattern,nocase,word)
    else:
        pos,newpos = self.plainHelper(s,i,j,pattern,nocase,word)

    if trace: g.trace('returns',pos,newpos)
    return pos,newpos
#@+node:ekr.20060526092203:regexHelper
def regexHelper (self,s,i,j,pattern,backwards,nocase):

    re_obj = self.re_obj # Use the pre-compiled object
    if not re_obj:
        g.trace('can not happen: no re_obj')
        return -1,-1

    # try:
        # flags = re.MULTILINE
        # if nocase: flags |= re.IGNORECASE
        # re_obj = re.compile(pattern,flags)
    # except Exception:
        # g.es('invalid regular expression:',pattern,color='blue')
        # self.errors += 1 # Abort the search.
        # return -1, -1

    if backwards: # Scan to the last match.  We must use search here.
        last_mo = None ; i = 0
        while i < len(s):
            mo = re_obj.search(s,i,j)
            if not mo: break
            i += 1 ; last_mo = mo
        mo = last_mo
    else:
        mo = re_obj.search(s,i,j)

    if 0:
        g.trace('i',i,'j',j,'s[i:j]',repr(s[i:j]),
            'mo.start',mo and mo.start(),'mo.end',mo and mo.end())

    while mo and 0 <= i < len(s):
        if mo.start() == mo.end():
            if backwards:
                # Search backward using match instead of search.
                i -= 1
                while 0 <= i < len(s):
                    mo = re_obj.match(s,i,j)
                    if mo: break
                    i -= 1
            else:
                i += 1 ; mo = re_obj.search(s,i,j)
        else:
            self.match_obj = mo
            return mo.start(),mo.end()
    self.match_obj = None
    return -1,-1
#@-node:ekr.20060526092203:regexHelper
#@+node:ekr.20060526140744:backwardsHelper
debugIndices = []

@
rfind(sub [,start [,end]])

Return the highest index in the string where substring sub is found, such that
sub is contained within s[start,end]. Optional arguments start and end are
interpreted as in slice notation. Return -1 on failure.
@c

def backwardsHelper (self,s,i,j,pattern,nocase,word):

    debug = False
    if nocase:
        s = s.lower() ; pattern = pattern.lower() # Bug fix: 10/5/06: At last the bug is found!
    pattern = self.replaceBackSlashes(pattern)
    n = len(pattern)

    if i < 0 or i > len(s) or j < 0 or j > len(s):
        g.trace('bad index: i = %s, j = %s' % (i,j))
        i = 0 ; j = len(s)

    if debug and (s and i == 0 and j == 0):
        g.trace('two zero indices')

    self.backwardAttempts += 1

    # short circuit the search: helps debugging.
    if s.find(pattern) == -1:
        if debug:
            self.debugCount += 1
            if self.debugCount < 50:
                g.trace(i,j,'len(s)',len(s),self.p.headString())
        return -1,-1

    if word:
        while 1:
            k = s.rfind(pattern,i,j)
            if debug: g.trace('**word** %3s %3s %5s -> %s %s' % (i,j,g.choose(j==len(s),'(end)',''),k,self.p.headString()))
            if k == -1: return -1, -1
            if self.matchWord(s,k,pattern):
                return k,k+n
            else:
                j = max(0,k-1)
    else:
        k = s.rfind(pattern,i,j)
        if debug: g.trace('%3s %3s %5s -> %s %s' % (i,j,g.choose(j==len(s),'(end)',''),k,self.p.headString()))
        if k == -1:
            return -1, -1
        else:
            return k,k+n
#@-node:ekr.20060526140744:backwardsHelper
#@+node:ekr.20060526093531:plainHelper
def plainHelper (self,s,i,j,pattern,nocase,word):

    trace = self.trace

    # if trace: g.trace(i,j,repr(s[i:i+20]),'pattern',repr(pattern),'word',repr(word))
    if trace: g.trace(i,j,repr(s[i:i+20]))

    if nocase:
        s = s.lower() ; pattern = pattern.lower()
    pattern = self.replaceBackSlashes(pattern)
    n = len(pattern)
    if word:
        while 1:
            k = s.find(pattern,i,j)
            # g.trace(k,n)
            if k == -1:
                if trace: g.trace('no match word',i)
                return -1, -1
            elif self.matchWord(s,k,pattern):
                if trace: g.trace('match word',k)
                return k, k + n
            else: i = k + n
    else:
        k = s.find(pattern,i,j)
        if k == -1:
            if trace: g.trace('no match word',i)
            return -1, -1
        else:
            if trace: g.trace('match', k)
            return k, k + n
#@-node:ekr.20060526093531:plainHelper
#@+node:ekr.20060526140744.1:matchWord
def matchWord(self,s,i,pattern):

    trace = self.trace

    pattern = self.replaceBackSlashes(pattern)
    if not s or not pattern or not g.match(s,i,pattern):
        if trace: g.trace('empty')
        return False

    pat1,pat2 = pattern[0],pattern[-1]
    # n = self.patternLen(pattern)
    n = len(pattern)
    ch1 = 0 <= i-1 < len(s) and s[i-1] or '.'
    ch2 = 0 <= i+n < len(s) and s[i+n] or '.'

    isWordPat1 = g.isWordChar(pat1)
    isWordPat2 = g.isWordChar(pat2)
    isWordCh1 = g.isWordChar(ch1)
    isWordCh2 = g.isWordChar(ch2)

    # g.trace('i',i,'ch1,ch2,pat',repr(ch1),repr(ch2),repr(pattern))

    inWord = isWordPat1 and isWordCh1 or isWordPat2 and isWordCh2
    if trace: g.trace('returns',not inWord)
    return not inWord

#@-node:ekr.20060526140744.1:matchWord
#@+node:ekr.20070105165924:replaceBackSlashes
def replaceBackSlashes (self,s):

    '''Carefully replace backslashes in a search pattern.'''

    # This is NOT the same as s.replace('\\n','\n').replace('\\t','\t').replace('\\\\','\\')
    # because there is no rescanning.

    i = 0
    while i + 1 < len(s):
        if s[i] == '\\':
            ch = s[i+1]
            if ch == '\\':
                s = s[:i] + s[i+1:] # replace \\ by \
            elif ch == 'n':
                s = s[:i] + '\n' + s[i+2:] # replace the \n by a newline
            elif ch == 't':
                s = s[:i] + '\t' + s[i+2:] # replace \t by a tab
            else:
                i += 1 # Skip the escaped character.
        i += 1

    if self.trace: g.trace(repr(s))
    return s
#@-node:ekr.20070105165924:replaceBackSlashes
#@-node:ekr.20060526081931:searchHelper & allies
#@-node:ekr.20031218072017.3077:search & helpers
#@-node:ekr.20071217032247:Compile regexp's once in findNextMatch
#@+node:ekr.20080218152956.2:Created menus very late in the creation process
@

This allows @menu items to refer to commands created by @button.
#@+node:ekr.20031218072017.1934:run
def run(fileName=None,pymacs=None,jyLeo=False,*args,**keywords):

    """Initialize and run Leo"""

    # __pychecker__ = '--no-argsused' # keywords not used.

    # print 'leo.py:run','fileName',fileName
    if not jyLeo and not isValidPython(): return
    << import leoGlobals and leoApp >>
    g.computeStandardDirectories()
    adjustSysPath(g)
    if pymacs:
        script = windowFlag = False
    else:
        script, windowFlag = getBatchScript() # Do early so we can compute verbose next.
    verbose = script is None
    g.app.batchMode = script is not None
    g.app.silentMode = '-silent' in sys.argv or '--silent' in sys.argv
    g.app.setLeoID(verbose=verbose) # Force the user to set g.app.leoID.
    << import leoNodes and leoConfig >>
    g.app.nodeIndices = leoNodes.nodeIndices(g.app.leoID)
    g.app.config = leoConfig.configClass()
    fileName,relativeFileName = completeFileName(fileName)
    reportDirectories(verbose)
    # Read settings *after* setting g.app.config.
    # Read settings *before* opening plugins.  This means if-gui has effect only in per-file settings.
    g.app.config.readSettingsFiles(fileName,verbose)
    g.app.setEncoding()
    if pymacs:
        createNullGuiWithScript(None)
    elif jyLeo:
        import leoSwingGui
        g.app.gui = leoSwingGui.swingGui()
    elif script:
        if windowFlag:
            g.app.createTkGui() # Creates global windows.
            g.app.gui.setScript(script)
            sys.args = []
        else:
            createNullGuiWithScript(script)
        fileName = None
    # Load plugins. Plugins may create g.app.gui.
    g.doHook("start1")
    if g.app.killed: return # Support for g.app.forceShutdown.
    # Create the default gui if needed.
    if g.app.gui == None: g.app.createTkGui() # Creates global windows.
    # Initialize tracing and statistics.
    g.init_sherlock(args)
    if g.app and g.app.use_psyco: startPsyco()
    # Clear g.app.initing _before_ creating the frame.
    g.app.initing = False # "idle" hooks may now call g.app.forceShutdown.
    # Create the main frame.  Show it and all queued messages.
    c,frame = createFrame(fileName,relativeFileName)
    if not frame: return
    g.app.trace_gc          = c.config.getBool('trace_gc')
    g.app.trace_gc_calls    = c.config.getBool('trace_gc_calls')
    g.app.trace_gc_verbose  = c.config.getBool('trace_gc_verbose')
    if g.app.disableSave:
        g.es("disabling save commands",color="red")
    g.app.writeWaitingLog()
    p = c.currentPosition()
    g.doHook("start2",c=c,p=p,v=p,fileName=fileName)
    if c.config.getBool('allow_idle_time_hook'):
        g.enableIdleTimeHook()
    if not fileName:
        c.redraw_now()
    c.bodyWantsFocus()
    g.app.gui.runMainLoop()
#@+node:ekr.20041219072112:<< import leoGlobals and leoApp >>
if jyLeo:

    print '*** starting jyLeo',sys.platform # will be something like java1.6.0_02

    ### This is a hack.
    ### The jyleo script in test.leo sets the cwd to g.app.loadDir
    ### Eventually, we will have to compute the equivalent here.

    path = os.path.join(os.getcwd()) ### ,'..','src')
    if path not in sys.path:
        print 'appending %s to sys.path' % path
        sys.path.append(path)
    if 0:
        print 'sys.path...'
        for s in sys.path: print s

# Import leoGlobals, but do NOT set g.
import leoGlobals
import leoApp

# Create the app.
leoGlobals.app = leoApp.LeoApp()

# **now** we can set g.
g = leoGlobals
assert(g.app)

if jyLeo:
    startJyleo(g)
#@-node:ekr.20041219072112:<< import leoGlobals and leoApp >>
#@+node:ekr.20041219072416.1:<< import leoNodes and leoConfig >>
import leoNodes
import leoConfig

# try:
    # import leoNodes
# except ImportError:
    # print "Error importing leoNodes.py"
    # import traceback ; traceback.print_exc()

# try:
    # import leoConfig
# except ImportError:
    # print "Error importing leoConfig.py"
    # import traceback ; traceback.print_exc()
#@-node:ekr.20041219072416.1:<< import leoNodes and leoConfig >>
#@-node:ekr.20031218072017.1934:run
#@+node:ekr.20031218072017.1624:createFrame (leo.py)
def createFrame (fileName,relativeFileName):

    """Create a LeoFrame during Leo's startup process."""

    import leoGlobals as g

    # Try to create a frame for the file.
    if fileName and g.os_path_exists(fileName):
        ok, frame = g.openWithFileName(relativeFileName or fileName,None)
        if ok: return frame.c,frame

    # Create a _new_ frame & indicate it is the startup window.
    c,frame = g.app.newLeoCommanderAndFrame(
        fileName=fileName,
        relativeFileName=relativeFileName,
        initEditCommanders=True)
    assert frame.c == c and c.frame == frame
    frame.setInitialWindowGeometry()
    frame.resizePanesToRatio(frame.ratio,frame.secondary_ratio)
    frame.startupWindow = True
    if c.chapterController:
        c.chapterController.finishCreate()
        c.setChanged(False) # Clear the changed flag set when creating the @chapters node.
    # Call the 'new' hook for compatibility with plugins.
    g.doHook("new",old_c=None,c=c,new_c=c)

    # New in Leo 4.4.8: create the menu as late as possible so it can use user commands.
    p = c.currentPosition()
    if not g.doHook("menu1",c=frame.c,p=p,v=p):
        frame.menu.createMenuBar(frame)
        c.updateRecentFiles(relativeFileName or fileName)
        g.doHook("menu2",c=frame.c,p=p,v=p)
        g.doHook("after-create-leo-frame",c=c)

    # Report the failure to open the file.
    if fileName:
        g.es("file not found:",fileName)

    return c,frame
#@-node:ekr.20031218072017.1624:createFrame (leo.py)
#@+node:ekr.20031218072017.2188:app.newLeoCommanderAndFrame
def newLeoCommanderAndFrame(self,
    fileName=None,
    relativeFileName=None,
    gui=None,initEditCommanders=True,updateRecentFiles=True):

    """Create a commander and its view frame for the Leo main window."""

    app = self

    import leoCommands

    if not fileName: fileName = ''
    if not relativeFileName: relativeFileName = ''
    if not gui: gui = g.app.gui
    << compute the window title >>

    # g.trace(fileName,relativeFileName)

    # Create an unfinished frame to pass to the commanders.
    frame = gui.createLeoFrame(title)

    # Create the commander and its subcommanders.
    c = leoCommands.Commands(frame,fileName,relativeFileName=relativeFileName)

    if not app.initing:
        g.doHook("before-create-leo-frame",c=c) # Was 'onCreate': too confusing.

    frame.finishCreate(c)
    c.finishCreate(initEditCommanders)

    # Finish initing the subcommanders.
    c.undoer.clearUndoState() # Menus must exist at this point.

    # if not g.app.initing:
        # g.doHook("after-create-leo-frame",c=c)

    return c,frame
#@+node:ekr.20031218072017.2189:<< compute the window title >>
# Set the window title and fileName
if fileName:
    title = g.computeWindowTitle(fileName)
else:
    s = "untitled"
    n = g.app.numberOfWindows
    if n > 0:
        s += str(n)
    title = g.computeWindowTitle(s)
    g.app.numberOfWindows = n+1
#@-node:ekr.20031218072017.2189:<< compute the window title >>
#@-node:ekr.20031218072017.2188:app.newLeoCommanderAndFrame
#@+node:ekr.20050920093543:c.finishCreate & helper
def finishCreate (self,initEditCommanders=True):  # New in 4.4.

    '''Finish creating the commander after frame.finishCreate.

    Important: this is the last step in the startup process.'''

    c = self ; p = c.currentPosition()
    c.miniBufferWidget = c.frame.miniBufferWidget
    # g.trace('Commands',c.fileName())

    # Create a keyHandler even if there is no miniBuffer.
    c.keyHandler = c.k = k = g.app.gui.createKeyHandlerClass(c,
        useGlobalKillbuffer=True,
        useGlobalRegisters=True)

    if initEditCommanders:
        # A 'real' .leo file.
        c.commandsDict = leoEditCommands.finishCreateEditCommanders(c)
        k.finishCreate()
    else:
        # A leoSettings.leo file.
        c.commandsDict = {}

    c.frame.log.finishCreate()
    c.bodyWantsFocusNow()
#@+node:ekr.20051007143620:printCommandsDict
def printCommandsDict (self):

    c = self

    print 'Commands...'
    keys = c.commandsDict.keys()
    keys.sort()
    for key in keys:
        command = c.commandsDict.get(key)
        print '%30s = %s' % (key,g.choose(command,command.__name__,'<None>'))
    print
#@-node:ekr.20051007143620:printCommandsDict
#@-node:ekr.20050920093543:c.finishCreate & helper
#@+node:ekr.20031218072017.4115:createMenuBar (Tkmenu)
def createMenuBar(self,frame):

    top = frame.top

    # Note: font setting has no effect here.
    topMenu = Tk.Menu(top,postcommand=self.updateAllMenus)

    # Do gui-independent stuff.
    self.setMenu("top",topMenu)
    self.createMenusFromTables()

    top.config(menu=topMenu) # Display the menu.
#@nonl
#@-node:ekr.20031218072017.4115:createMenuBar (Tkmenu)
#@+node:ekr.20031218072017.2052:g.openWithFileName
def openWithFileName(fileName,old_c,
    enableLog=True,gui=None,readAtFileNodesFlag=True):

    """Create a Leo Frame for the indicated fileName if the file exists."""

    if not fileName or len(fileName) == 0:
        return False, None

    def munge(name):
        return g.os_path_normpath(name or '').lower()

    # Create a full, normalized, Unicode path name, preserving case.
    relativeFileName = g.os_path_normpath(fileName)
    fileName = g.os_path_normpath(g.os_path_abspath(fileName))
    # g.trace(relativeFileName,'-->',fileName)

    # If the file is already open just bring its window to the front.
    theList = app.windowList
    for frame in theList:
        if munge(fileName) == munge(frame.c.mFileName):
            frame.bringToFront()
            frame.c.setLog()
            return True, frame
    if old_c:
        # New in 4.4: We must read the file *twice*.
        # The first time sets settings for the later call to c.finishCreate.
        # g.trace('***** prereading',fileName)
        c2 = g.app.config.openSettingsFile(fileName)
        if c2: g.app.config.updateSettings(c2,localFlag=True)
        g.doHook('open0')

    # Open the file in binary mode to allow 0x1a in bodies & headlines.
    theFile,isZipped = g.openLeoOrZipFile(fileName)
    if not theFile: return False, None
    c,frame = app.newLeoCommanderAndFrame(
        fileName=fileName,
        relativeFileName=relativeFileName,
        gui=gui)
    assert frame.c == c and c.frame == frame
    c.isZipped = isZipped
    frame.log.enable(enableLog)
    g.app.writeWaitingLog() # New in 4.3: write queued log first.
    c.beginUpdate()
    try:
        if not g.doHook("open1",old_c=old_c,c=c,new_c=c,fileName=fileName):
            c.setLog()
            app.lockLog()
            frame.c.fileCommands.open(
                theFile,fileName,
                readAtFileNodesFlag=readAtFileNodesFlag) # closes file.
            app.unlockLog()
            for z in g.app.windowList: # Bug fix: 2007/12/07: don't change frame var.
                # The recent files list has been updated by c.updateRecentFiles.
                z.c.config.setRecentFiles(g.app.config.recentFiles)
        # Bug fix in 4.4.
        frame.openDirectory = g.os_path_abspath(g.os_path_dirname(fileName))
        g.doHook("open2",old_c=old_c,c=c,new_c=c,fileName=fileName)
        p = c.currentPosition()
        # New in Leo 4.4.8: create the menu as late as possible so it can use user commands.
        if not g.doHook("menu1",c=c,p=p,v=p):
            frame.menu.createMenuBar(frame)
            c.updateRecentFiles(relativeFileName or fileName)
            g.doHook("menu2",c=frame.c,p=p,v=p)
            g.doHook("after-create-leo-frame",c=c)

    finally:
        c.endUpdate()
        assert frame.c == c and c.frame == frame
        # chapterController.finishCreate must be called after the first real redraw
        # because it requires a valid value for c.rootPosition().
        if c.chapterController:
            c.chapterController.finishCreate()
        k = c.k
        if k: k.setInputState(k.unboundKeyAction)
        if c.config.getBool('outline_pane_has_initial_focus'):
            c.treeWantsFocusNow()
        else:
            c.bodyWantsFocusNow()
    return True, frame
#@nonl
#@-node:ekr.20031218072017.2052:g.openWithFileName
#@+node:ekr.20031218072017.1623:new
def new (self,event=None,gui=None):

    '''Create a new Leo window.'''

    c,frame = g.app.newLeoCommanderAndFrame(fileName=None,relativeFileName=None,gui=gui)

    # Needed for plugins.
    g.doHook("new",old_c=self,c=c,new_c=c)
    # Use the config params to set the size and location of the window.
    c.beginUpdate()
    try:
        frame.setInitialWindowGeometry()
        frame.deiconify()
        frame.lift()
        frame.resizePanesToRatio(frame.ratio,frame.secondary_ratio) # Resize the _new_ frame.
        t = leoNodes.tnode()
        v = leoNodes.vnode(t)
        p = leoNodes.position(v,[])
        v.initHeadString("NewHeadline")
        v.moveToRoot(oldRoot=None)
        c.setRootVnode(v) # New in Leo 4.4.2.
        c.editPosition(p)
        # New in Leo 4.4.8: create the menu as late as possible so it can use user commands.
        p = c.currentPosition()
        if not g.doHook("menu1",c=c,p=p,v=p):
            frame.menu.createMenuBar(frame)
            c.updateRecentFiles(fileName=None)
            g.doHook("menu2",c=frame.c,p=p,v=p)
            g.doHook("after-create-leo-frame",c=c)

    finally:
        c.endUpdate()
        # chapterController.finishCreate must be called after the first real redraw
        # because it requires a valid value for c.rootPosition().
        if c.config.getBool('use_chapters') and c.chapterController:
            c.chapterController.finishCreate()
            frame.c.setChanged(False) # Clear the changed flag set when creating the @chapters node.
        if c.config.getBool('outline_pane_has_initial_focus'):
            c.treeWantsFocusNow()
        else:
            c.bodyWantsFocusNow()
    return c # For unit test.
#@-node:ekr.20031218072017.1623:new
#@-node:ekr.20080218152956.2:Created menus very late in the creation process
#@+node:ekr.20070613103409:Improved marks/recent buttons plugin
- Added show-marks-dialog and show-recent-sections-dialog commands.
- Select an item initially.
- Added bindings for up and down arrows.
#@nonl
#@-node:ekr.20070613103409:Improved marks/recent buttons plugin
#@+node:ekr.20080312100126.1:Added support for @commands trees in settings files
#@+node:ekr.20061031131434.131:k.registerCommand
def registerCommand (self,commandName,shortcut,func,pane='all',verbose=False):

    '''Make the function available as a minibuffer command,
    and optionally attempt to bind a shortcut.

    You can wrap any method in a callback function, so the
    restriction to functions is not significant.'''

    # g.trace(commandName,g.callers())

    k = self ; c = k.c
    f = c.commandsDict.get(commandName)
    verbose = (False or verbose) and not g.app.unitTesting
    if f and f.__name__ != 'dummyCallback' and verbose:
        g.es_print('redefining',commandName, color='red')

    c.commandsDict [commandName] = func
    k.inverseCommandsDict [func.__name__] = commandName
    # g.trace('leoCommands %24s = %s' % (func.__name__,commandName))

    if shortcut:
        stroke = k.shortcutFromSetting(shortcut)
    elif commandName.lower() == 'shortcut': # Causes problems.
        stroke = None
    else:
        # Try to get a shortcut from leoSettings.leo.
        junk,bunchList = c.config.getShortcut(commandName)
        for bunch in bunchList:
            accel2 = bunch.val ; pane2 = bunch.pane
            if accel2 and not pane2.endswith('-mode'):
                shortcut2 = accel2
                stroke = k.shortcutFromSetting(shortcut2)
                if stroke: break
        else: stroke = None

    if stroke:
        # g.trace('stroke',stroke,'pane',pane,commandName,g.callers())
        ok = k.bindKey (pane,stroke,func,commandName) # Must be a stroke.
        k.makeMasterGuiBinding(stroke) # Must be a stroke.
        if verbose and ok and not g.app.silentMode:
            g.es_print('','@command: %s = %s' % (
                commandName,k.prettyPrintKey(stroke)),color='blue')
            if 0:
                d = k.masterBindingsDict.get('button',{})
                g.print_dict(d)
        c.frame.tree.setBindings()
    elif verbose and not g.app.silentMode:
        g.es_print('','@command: %s' % (commandName),color='blue')

    # Fixup any previous abbreviation to press-x-button commands.
    if commandName.startswith('press-') and commandName.endswith('-button'):
        d = c.config.getAbbrevDict()
            # Keys are full command names, values are abbreviations.
        if commandName in d.values():
            for key in d.keys():
                if d.get(key) == commandName:
                    c.commandsDict [key] = c.commandsDict.get(commandName)
                    break
#@-node:ekr.20061031131434.131:k.registerCommand
#@+node:ekr.20080312071248.21:Document @button/s and @command/s nodes in settings files
#@-node:ekr.20080312071248.21:Document @button/s and @command/s nodes in settings files
#@+node:EKR.20040614071102.1:g.getScript
def getScript (c,p,useSelectedText=True,forcePythonSentinels=True,useSentinels=True):

    '''Return the expansion of the selected text of node p.
    Return the expansion of all of node p's body text if there
    is p is not the current node or if there is no text selection.'''

    at = c.atFileCommands ; w = c.frame.body.bodyCtrl
    p1 = p and p.copy()
    if not p:
        p = c.currentPosition()
    try:
        if g.app.inBridge:
            s = p.bodyString()
        elif p1:
            s = p.bodyString() # Bug fix: Leo 8.8.4.
        elif p == c.currentPosition():
            if useSelectedText and w.hasSelection():
                s = w.getSelectedText()
            else:
                s = w.getAllText()
        else:
            s = p.bodyString()
        # Remove extra leading whitespace so the user may execute indented code.
        s = g.removeExtraLws(s,c.tab_width)
        if s.strip():
            g.app.scriptDict["script1"]=s
            script = at.writeFromString(p.copy(),s,
                forcePythonSentinels=forcePythonSentinels,
                useSentinels=useSentinels)
            script = script.replace("\r\n","\n") # Use brute force.
            # Important, the script is an **encoded string**, not a unicode string.
            g.app.scriptDict["script2"]=script
        else: script = ''
    except Exception:
        g.es_print("unexpected exception in g.getScript")
        g.es_exception()
        script = ''

    # g.trace(type(script),repr(script))
    return script
#@-node:EKR.20040614071102.1:g.getScript
#@-node:ekr.20080312100126.1:Added support for @commands trees in settings files
#@-node:ekr.20080310093038.1:Features
#@-node:ekr.20080324105006.1:Previous
#@+node:ekr.20080315083057.10:b3
#@+node:ekr.20080315115427.575:Fixed bugs
#@+node:ekr.20080315083057.2:Corrected docs: unit tests are in test/unitTest.leo
#@-node:ekr.20080315083057.2:Corrected docs: unit tests are in test/unitTest.leo
#@+node:ekr.20080315083057.1:Removed '.' from leoID
# This fixes a bug that can corrupt .leo files.
#@nonl
#@+node:ekr.20031218072017.1978:app.setLeoID
def setLeoID (self,verbose=True):

    tag = ".leoID.txt"
    homeDir = g.app.homeDir
    globalConfigDir = g.app.globalConfigDir
    loadDir = g.app.loadDir

    verbose = not g.app.unitTesting
    << return if we can set leoID from sys.leoID >>
    << return if we can set leoID from "leoID.txt" >>
    << return if we can set leoID from os.getenv('USER') >>
    << put up a dialog requiring a valid id >>
    << attempt to create leoID.txt >>
#@+node:ekr.20031218072017.1979:<< return if we can set leoID from sys.leoID>>
# This would be set by in Python's sitecustomize.py file.

# 7/2/04: Use hasattr & getattr to suppress pychecker warning.
# We also have to use a "non-constant" attribute to suppress another warning!

nonConstantAttr = "leoID"

if hasattr(sys,nonConstantAttr):
    g.app.leoID = getattr(sys,nonConstantAttr)
    if verbose and not g.app.unitTesting:
        g.es_print("leoID=",g.app.leoID,spaces=False,color='red')
    # Bug fix: 2008/3/15: periods in the id field of a gnx will corrupt the .leo file!
    g.app.leoID = g.app.leoID.replace('.','-')
    return
else:
    g.app.leoID = None
#@-node:ekr.20031218072017.1979:<< return if we can set leoID from sys.leoID>>
#@+node:ekr.20031218072017.1980:<< return if we can set leoID from "leoID.txt" >>
for theDir in (homeDir,globalConfigDir,loadDir):
    # N.B. We would use the _working_ directory if theDir is None!
    if theDir:
        try:
            fn = g.os_path_join(theDir,tag)
            f = open(fn,'r')
            s = f.readline()
            f.close()
            if s and len(s) > 0:
                g.app.leoID = s.strip()
                # Bug fix: 2008/3/15: periods in the id field of a gnx will corrupt the .leo file!
                g.app.leoID = g.app.leoID.replace('.','-')
                if verbose and not g.app.unitTesting:
                    g.es('leoID=',g.app.leoID,' (in ',theDir,')',spaces=False,color="red")
                return
            elif verbose and not g.app.unitTesting:
                g.es('empty ',tag,' (in ',theDir,')',spaces=False,color = "red")
        except IOError:
            g.app.leoID = None
        except Exception:
            g.app.leoID = None
            g.es_print('unexpected exception in app.setLeoID',color='red')
            g.es_exception()
#@-node:ekr.20031218072017.1980:<< return if we can set leoID from "leoID.txt" >>
#@+node:ekr.20060211140947.1:<< return if we can set leoID from os.getenv('USER') >>
try:
    theId = os.getenv('USER')
    if theId:
        if verbose and not g.app.unitTesting:
            g.es("using os.getenv('USER'):",repr(theId),color='red')
        g.app.leoID = theId
        # Bug fix: 2008/3/15: periods in the id field of a gnx will corrupt the .leo file!
        g.app.leoID = g.app.leoID.replace('.','-')
        return

except Exception:
    pass
#@-node:ekr.20060211140947.1:<< return if we can set leoID from os.getenv('USER') >>
#@+node:ekr.20031218072017.1981:<< put up a dialog requiring a valid id >>
# New in 4.1: get an id for gnx's.  Plugins may set g.app.leoID.

# Create an emergency gui and a Tk root window.
g.app.createTkGui("startup")

# Bug fix: 2/6/05: put result in g.app.leoID.
g.app.leoID = g.app.gui.runAskLeoIDDialog()

# Bug fix: 2008/3/15: periods in the id field of a gnx will corrupt the .leo file!
g.app.leoID = g.app.leoID.replace('.','-')

# g.trace(g.app.leoID)
g.es('leoID=',repr(g.app.leoID),spaces=False,color="blue")
#@-node:ekr.20031218072017.1981:<< put up a dialog requiring a valid id >>
#@+node:ekr.20031218072017.1982:<< attempt to create leoID.txt >>
for theDir in (homeDir,globalConfigDir,loadDir):
    # N.B. We would use the _working_ directory if theDir is None!
    if theDir:
        try:
            fn = g.os_path_join(theDir,tag)
            f = open(fn,'w')
            f.write(g.app.leoID)
            f.close()
            if g.os_path_exists(fn):
                g.es_print('',tag,'created in',theDir,color='red')
                return
        except IOError:
            pass

        g.es('can not create',tag,'in',theDir,color='red')
#@-node:ekr.20031218072017.1982:<< attempt to create leoID.txt >>
#@-node:ekr.20031218072017.1978:app.setLeoID
#@-node:ekr.20080315083057.1:Removed '.' from leoID
#@+node:ekr.20080315115427.4:Updated history chapter
#@-node:ekr.20080315115427.4:Updated history chapter
#@+node:ekr.20080318153052.1:Fixed Open With menu
@nocolor

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

The open_with menu is always empty.

What I did:

- The open with plugin now uses the new 'menu2' event handler.

@color
#@+node:ekr.20031218072017.2823:openWith and allies
def openWith(self,event=None,data=None):

    """This routine handles the items in the Open With... menu.

    These items can only be created by createOpenWithMenuFromTable().
    Typically this would be done from the "open2" hook.

    New in 4.3: The "os.spawnv" now works. You may specify arguments to spawnv
    using a list, e.g.:

    openWith("os.spawnv", ["c:/prog.exe","--parm1","frog","--switch2"], None)
    """

    c = self ; p = c.currentPosition()
    n = data and len(data) or 0
    if n != 3:
        g.trace('bad data, length must be 3, got %d' % n)
        return
    try:
        openType,arg,ext=data
        if not g.doHook("openwith1",c=c,p=p,v=p.v,openType=openType,arg=arg,ext=ext):
            g.enableIdleTimeHook(idleTimeDelay=100)
            << set ext based on the present language >>
            << create or reopen temp file, testing for conflicting changes >>
            << execute a command to open path in external editor >>
        g.doHook("openwith2",c=c,p=p,v=p.v,openType=openType,arg=arg,ext=ext)
    except Exception:
        g.es("unexpected exception in c.openWith")
        g.es_exception()

    return "break"
#@+node:ekr.20031218072017.2824:<< set ext based on the present language >>
if not ext:
    theDict = g.scanDirectives(c)
    language = theDict.get("language")
    ext = g.app.language_extension_dict.get(language)
    # print language,ext
    if ext == None:
        ext = "txt"

if ext[0] != ".":
    ext = "."+ext

# print "ext",ext
#@-node:ekr.20031218072017.2824:<< set ext based on the present language >>
#@+node:ekr.20031218072017.2825:<< create or reopen temp file, testing for conflicting changes >>
theDict = None ; path = None
<< set dict and path if a temp file already refers to p.v.t >>
if path:
    << create or recreate temp file as needed >>
else:
    path = c.createOpenWithTempFile(p,ext)

if not path:
    return # An error has occured.
#@+node:ekr.20031218072017.2826:<<set dict and path if a temp file already refers to p.v.t >>
searchPath = c.openWithTempFilePath(p,ext)

if g.os_path_exists(searchPath):
    for theDict in g.app.openWithFiles:
        if p.v == theDict.get('v') and searchPath == theDict.get("path"):
            path = searchPath
            break
#@-node:ekr.20031218072017.2826:<<set dict and path if a temp file already refers to p.v.t >>
#@+node:ekr.20031218072017.2827:<< create or recreate temp file as needed >>
@ We test for changes in both p and the temp file:

- If only p's body text has changed, we recreate the temp file.
- If only the temp file has changed, do nothing here.
- If both have changed we must prompt the user to see which code to use.
@c

encoding = theDict.get("encoding")
old_body = theDict.get("body")
new_body = p.bodyString()
new_body = g.toEncodedString(new_body,encoding,reportErrors=True)

old_time = theDict.get("time")
try:
    new_time = g.os_path_getmtime(path)
except:
    new_time = None

body_changed = old_body != new_body
temp_changed = old_time != new_time

if body_changed and temp_changed:
    << Raise dialog about conflict and set result >>
    if result == "cancel": return
    rewrite = result == "outline"
else:
    rewrite = body_changed

if rewrite:
    path = c.createOpenWithTempFile(p,ext)
else:
    g.es("reopening:",g.shortFileName(path),color="blue")
#@+node:ekr.20031218072017.2828:<< Raise dialog about conflict and set result >>
message = (
    "Conflicting changes in outline and temp file\n\n" +
    "Do you want to use the code in the outline or the temp file?\n\n")

result = g.app.gui.runAskYesNoCancelDialog(c,
    "Conflict!", message,
    yesMessage = "Outline",
    noMessage = "File",
    defaultButton = "Cancel")
#@-node:ekr.20031218072017.2828:<< Raise dialog about conflict and set result >>
#@-node:ekr.20031218072017.2827:<< create or recreate temp file as needed >>
#@-node:ekr.20031218072017.2825:<< create or reopen temp file, testing for conflicting changes >>
#@+node:ekr.20031218072017.2829:<< execute a command to open path in external editor >>
try:
    if arg == None: arg = ""
    shortPath = path # g.shortFileName(path)
    if openType == "os.system":
        if 1:
            # This works, _provided_ that arg does not contain blanks.  Sheesh.
            command = 'os.system(%s)' % (arg+shortPath)
            os.system(arg+shortPath)
        else:
            # XP does not like this format!
            command = 'os.system("%s" "%s")' % (arg,shortPath)
            os.system('"%s" "%s"' % (arg,shortPath))
    elif openType == "os.startfile":
        command = "os.startfile(%s)" % (arg+shortPath)
        os.startfile(arg+path)
    elif openType == "exec":
        command = "exec(%s)" % (arg+shortPath)
        exec arg+path in {}
    elif openType == "os.spawnl":
        filename = g.os_path_basename(arg)
        command = "os.spawnl(%s,%s,%s)" % (arg,filename,path)
        apply(os.spawnl,(os.P_NOWAIT,arg,filename,path))
    elif openType == "os.spawnv":
        filename = os.path.basename(arg[0]) 
        vtuple = arg[1:]
        vtuple.insert(0, filename)
            # add the name of the program as the first argument.
            # Change suggested by Jim Sizelove.
        vtuple.append(path)
        command = "os.spawnv(%s,%s)" % (arg[0],repr(vtuple))
        apply(os.spawnv,(os.P_NOWAIT,arg[0],vtuple))
    # This clause by Jim Sizelove.
    elif openType == "subprocess.Popen":
        if isinstance(arg, basestring):
            vtuple = arg + " " + path
        elif isinstance(arg, (list, tuple)):
            vtuple = arg[:]
            vtuple.append(path)
        command = "subprocess.Popen(%s)" % repr(vtuple)
        if subprocess:
            subprocess.Popen(vtuple)
        else:
            g.trace('Can not import subprocess.  Skipping: "%s"' % command)
    else:
        command="bad command:"+str(openType)
        g.trace(command)
except Exception:
    g.es("exception executing:",command)
    g.es_exception()
#@-node:ekr.20031218072017.2829:<< execute a command to open path in external editor >>
#@+node:ekr.20031218072017.2830:createOpenWithTempFile
def createOpenWithTempFile (self,p,ext):

    c = self
    path = c.openWithTempFilePath(p,ext)
    try:
        if g.os_path_exists(path):
            g.es("recreating:  ",g.shortFileName(path),color="red")
        else:
            g.es("creating:  ",g.shortFileName(path),color="blue")
        theFile = open(path,"w")
        # Convert s to whatever encoding is in effect.
        s = p.bodyString()
        theDict = g.scanDirectives(c,p=p)
        encoding = theDict.get("encoding",None)
        if encoding == None:
            encoding = c.config.default_derived_file_encoding
        s = g.toEncodedString(s,encoding,reportErrors=True) 
        theFile.write(s)
        theFile.flush()
        theFile.close()
        try:    time = g.os_path_getmtime(path)
        except: time = None
        # g.es("time: " + str(time))
        # New in 4.3: theDict now contains both 'p' and 'v' entries, of the expected type.
        theDict = {
            "body":s, "c":c, "encoding":encoding,
            "f":theFile, "path":path, "time":time,
            "p":p, "v":p.v }
        << remove previous entry from app.openWithFiles if it exists >>
        g.app.openWithFiles.append(theDict)
        return path
    except:
        if theFile:
            theFile.close()
        theFile = None
        g.es("exception creating temp file",color="red")
        g.es_exception()
        return None
#@+node:ekr.20031218072017.2831:<< remove previous entry from app.openWithFiles if it exists >>
for d in g.app.openWithFiles[:]:
    p2 = d.get("p")
    if p.v.t == p2.v.t:
        # print "removing previous entry in g.app.openWithFiles for",p.headString()
        g.app.openWithFiles.remove(d)
#@-node:ekr.20031218072017.2831:<< remove previous entry from app.openWithFiles if it exists >>
#@-node:ekr.20031218072017.2830:createOpenWithTempFile
#@+node:ekr.20031218072017.2832:c.openWithTempFilePath
def openWithTempFilePath (self,p,ext):

    """Return the path to the temp file corresponding to p and ext."""

    name = "LeoTemp_%s_%s%s" % (
        str(id(p.v.t)),
        g.sanitize_filename(p.headString()),
        ext)

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

    td = g.os_path_abspath(tempfile.gettempdir())

    path = g.os_path_join(td,name)

    return path
#@-node:ekr.20031218072017.2832:c.openWithTempFilePath
#@-node:ekr.20031218072017.2823:openWith and allies
#@+node:ekr.20031218072017.4116:createOpenWithMenuFromTable & helper
def createOpenWithMenuFromTable (self,table):

    '''Entries in the table passed to createOpenWithMenuFromTable are
tuples of the form (commandName,shortcut,data).

- command is one of "os.system", "os.startfile", "os.spawnl", "os.spawnv" or "exec".
- shortcut is a string describing a shortcut, just as for createMenuItemsFromTable.
- data is a tuple of the form (command,arg,ext).

Leo executes command(arg+path) where path is the full path to the temp file.
If ext is not None, the temp file has the given extension.
Otherwise, Leo computes an extension based on the @language directive in effect.'''

    c = self.c
    g.app.openWithTable = table # Override any previous table.
    # Delete the previous entry.
    parent = self.getMenu("File")
    if not parent:
        if not g.app.batchMode:
            g.es('','createOpenWithMenuFromTable:','no File menu',color="red")
        return

    label = self.getRealMenuName("Open &With...")
    amp_index = label.find("&")
    label = label.replace("&","")
    try:
        index = parent.index(label)
        parent.delete(index)
    except:
        try:
            index = parent.index("Open With...")
            parent.delete(index)
        except: return
    # Create the Open With menu.
    openWithMenu = self.createOpenWithMenu(parent,label,index,amp_index)
    self.setMenu("Open With...",openWithMenu)
    # Create the menu items in of the Open With menu.
    for entry in table:
        if len(entry) != 3: # 6/22/03
            g.es('','createOpenWithMenuFromTable:','invalid data',color="red")
            return
    self.createOpenWithMenuItemsFromTable(openWithMenu,table)
    for entry in table:
        name,shortcut,data = entry
        c.k.bindOpenWith (shortcut,name,data)
#@+node:ekr.20051022043608.1:createOpenWithMenuItemsFromTable
def createOpenWithMenuItemsFromTable (self,menu,table):

    '''Create an entry in the Open with Menu from the table.

    Each entry should be a sequence with 2 or 3 elements.'''

    c = self.c ; k = c.k

    if g.app.unitTesting: return

    for data in table:
        << get label, accelerator & command or continue >>
        # g.trace(label,accelerator)
        realLabel = self.getRealMenuName(label)
        underline=realLabel.find("&")
        realLabel = realLabel.replace("&","")
        callback = self.defineOpenWithMenuCallback(openWithData)

        self.add_command(menu,label=realLabel,
            accelerator=accelerator or '',
            command=callback,underline=underline)
#@+node:ekr.20051022043713.1:<< get label, accelerator & command or continue >>
ok = (
    type(data) in (type(()), type([])) and
    len(data) in (2,3)
)

if ok:
    if len(data) == 2:
        label,openWithData = data ; accelerator = None
    else:
        label,accelerator,openWithData = data
        accelerator = k.shortcutFromSetting(accelerator)
        accelerator = accelerator and g.stripBrackets(k.prettyPrintKey(accelerator))
else:
    g.trace('bad data in Open With table: %s' % repr(data))
    continue # Ignore bad data
#@-node:ekr.20051022043713.1:<< get label, accelerator & command or continue >>
#@-node:ekr.20051022043608.1:createOpenWithMenuItemsFromTable
#@-node:ekr.20031218072017.4116:createOpenWithMenuFromTable & helper
#@+node:ekr.20080323093939.1:Found: menu1
#@+node:ekr.20031218072017.1623:new
def new (self,event=None,gui=None):

    '''Create a new Leo window.'''

    c,frame = g.app.newLeoCommanderAndFrame(fileName=None,relativeFileName=None,gui=gui)

    # Needed for plugins.
    g.doHook("new",old_c=self,c=c,new_c=c)
    # Use the config params to set the size and location of the window.
    c.beginUpdate()
    try:
        frame.setInitialWindowGeometry()
        frame.deiconify()
        frame.lift()
        frame.resizePanesToRatio(frame.ratio,frame.secondary_ratio) # Resize the _new_ frame.
        t = leoNodes.tnode()
        v = leoNodes.vnode(t)
        p = leoNodes.position(v,[])
        v.initHeadString("NewHeadline")
        v.moveToRoot(oldRoot=None)
        c.setRootVnode(v) # New in Leo 4.4.2.
        c.editPosition(p)
        # New in Leo 4.4.8: create the menu as late as possible so it can use user commands.
        p = c.currentPosition()
        if not g.doHook("menu1",c=c,p=p,v=p):
            frame.menu.createMenuBar(frame)
            c.updateRecentFiles(fileName=None)
            g.doHook("menu2",c=frame.c,p=p,v=p)
            g.doHook("after-create-leo-frame",c=c)

    finally:
        c.endUpdate()
        # chapterController.finishCreate must be called after the first real redraw
        # because it requires a valid value for c.rootPosition().
        if c.config.getBool('use_chapters') and c.chapterController:
            c.chapterController.finishCreate()
            frame.c.setChanged(False) # Clear the changed flag set when creating the @chapters node.
        if c.config.getBool('outline_pane_has_initial_focus'):
            c.treeWantsFocusNow()
        else:
            c.bodyWantsFocusNow()
    return c # For unit test.
#@-node:ekr.20031218072017.1623:new
#@+node:ekr.20031218072017.1624:createFrame (leo.py)
def createFrame (fileName,relativeFileName):

    """Create a LeoFrame during Leo's startup process."""

    import leoGlobals as g

    # Try to create a frame for the file.
    if fileName and g.os_path_exists(fileName):
        ok, frame = g.openWithFileName(relativeFileName or fileName,None)
        if ok: return frame.c,frame

    # Create a _new_ frame & indicate it is the startup window.
    c,frame = g.app.newLeoCommanderAndFrame(
        fileName=fileName,
        relativeFileName=relativeFileName,
        initEditCommanders=True)
    assert frame.c == c and c.frame == frame
    frame.setInitialWindowGeometry()
    frame.resizePanesToRatio(frame.ratio,frame.secondary_ratio)
    frame.startupWindow = True
    if c.chapterController:
        c.chapterController.finishCreate()
        c.setChanged(False) # Clear the changed flag set when creating the @chapters node.
    # Call the 'new' hook for compatibility with plugins.
    g.doHook("new",old_c=None,c=c,new_c=c)

    # New in Leo 4.4.8: create the menu as late as possible so it can use user commands.
    p = c.currentPosition()
    if not g.doHook("menu1",c=frame.c,p=p,v=p):
        frame.menu.createMenuBar(frame)
        c.updateRecentFiles(relativeFileName or fileName)
        g.doHook("menu2",c=frame.c,p=p,v=p)
        g.doHook("after-create-leo-frame",c=c)

    # Report the failure to open the file.
    if fileName:
        g.es("file not found:",fileName)

    return c,frame
#@-node:ekr.20031218072017.1624:createFrame (leo.py)
#@+node:ekr.20031218072017.2052:g.openWithFileName
def openWithFileName(fileName,old_c,
    enableLog=True,gui=None,readAtFileNodesFlag=True):

    """Create a Leo Frame for the indicated fileName if the file exists."""

    if not fileName or len(fileName) == 0:
        return False, None

    def munge(name):
        return g.os_path_normpath(name or '').lower()

    # Create a full, normalized, Unicode path name, preserving case.
    relativeFileName = g.os_path_normpath(fileName)
    fileName = g.os_path_normpath(g.os_path_abspath(fileName))
    # g.trace(relativeFileName,'-->',fileName)

    # If the file is already open just bring its window to the front.
    theList = app.windowList
    for frame in theList:
        if munge(fileName) == munge(frame.c.mFileName):
            frame.bringToFront()
            frame.c.setLog()
            return True, frame
    if old_c:
        # New in 4.4: We must read the file *twice*.
        # The first time sets settings for the later call to c.finishCreate.
        # g.trace('***** prereading',fileName)
        c2 = g.app.config.openSettingsFile(fileName)
        if c2: g.app.config.updateSettings(c2,localFlag=True)
        g.doHook('open0')

    # Open the file in binary mode to allow 0x1a in bodies & headlines.
    theFile,isZipped = g.openLeoOrZipFile(fileName)
    if not theFile: return False, None
    c,frame = app.newLeoCommanderAndFrame(
        fileName=fileName,
        relativeFileName=relativeFileName,
        gui=gui)
    assert frame.c == c and c.frame == frame
    c.isZipped = isZipped
    frame.log.enable(enableLog)
    g.app.writeWaitingLog() # New in 4.3: write queued log first.
    c.beginUpdate()
    try:
        if not g.doHook("open1",old_c=old_c,c=c,new_c=c,fileName=fileName):
            c.setLog()
            app.lockLog()
            frame.c.fileCommands.open(
                theFile,fileName,
                readAtFileNodesFlag=readAtFileNodesFlag) # closes file.
            app.unlockLog()
            for z in g.app.windowList: # Bug fix: 2007/12/07: don't change frame var.
                # The recent files list has been updated by c.updateRecentFiles.
                z.c.config.setRecentFiles(g.app.config.recentFiles)
        # Bug fix in 4.4.
        frame.openDirectory = g.os_path_abspath(g.os_path_dirname(fileName))
        g.doHook("open2",old_c=old_c,c=c,new_c=c,fileName=fileName)
        p = c.currentPosition()
        # New in Leo 4.4.8: create the menu as late as possible so it can use user commands.
        if not g.doHook("menu1",c=c,p=p,v=p):
            frame.menu.createMenuBar(frame)
            c.updateRecentFiles(relativeFileName or fileName)
            g.doHook("menu2",c=frame.c,p=p,v=p)
            g.doHook("after-create-leo-frame",c=c)

    finally:
        c.endUpdate()
        assert frame.c == c and c.frame == frame
        # chapterController.finishCreate must be called after the first real redraw
        # because it requires a valid value for c.rootPosition().
        if c.chapterController:
            c.chapterController.finishCreate()
        k = c.k
        if k: k.setInputState(k.unboundKeyAction)
        if c.config.getBool('outline_pane_has_initial_focus'):
            c.treeWantsFocusNow()
        else:
            c.bodyWantsFocusNow()
    return True, frame
#@nonl
#@-node:ekr.20031218072017.2052:g.openWithFileName
#@-node:ekr.20080323093939.1:Found: menu1
#@-node:ekr.20080318153052.1:Fixed Open With menu
#@+node:ekr.20080322060140.1:Fixed @lineending botch
@nocolor

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

@lineending don't work, leo always save files in UNIX way 

@color
#@nonl
#@+node:ekr.20031218072017.1388:g.scanAtLineendingDirective
def scanAtLineendingDirective(theDict):

    """Scan the @lineending directive at s[theDict["lineending"]:].

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

    e = theDict.get('lineending')

    if e in ("cr","crlf","lf","nl","platform"):
        lineending = g.getOutputNewline(name=e)
        # g.trace(e,lineending)
        return lineending
    else:
        # g.es("invalid @lineending directive:",e,color="red")
        return None
#@-node:ekr.20031218072017.1388:g.scanAtLineendingDirective
#@+node:ekr.20031218072017.1386:getOutputNewline
def getOutputNewline (c=None,name=None):

    '''Convert the name of a line ending to the line ending itself.

    Priority:
    - Use name if name given
    - Use c.config.output_newline if c given,
    - Otherwise use g.app.config.output_newline.'''

    if name: s = name
    elif c:  s = c.config.output_newline
    else:    s = app.config.output_newline

    if not s: s = ''
    s = s.lower()
    if s in ( "nl","lf"): s = '\n'
    elif s == "cr": s = '\r'
    elif s == "platform": s = os.linesep  # 12/2/03: emakital
    elif s == "crlf": s = "\r\n"
    else: s = '\n' # Default for erroneous values.
    # g.trace(c,name,c.config.output_newline,'returns',repr(s))
    return s
#@-node:ekr.20031218072017.1386:getOutputNewline
#@+node:ekr.20041005105605.205:outputStringWithLineEndings
# Write the string s as-is except that we replace '\n' with the proper line ending.

def outputStringWithLineEndings (self,s):

    # Calling self.onl() runs afoul of queued newlines.
    self.os(s.replace('\n',self.output_newline))
#@-node:ekr.20041005105605.205:outputStringWithLineEndings
#@+node:ekr.20041005105605.222:atFile.scanAllDirectives
@ Once a directive is seen, no other related directives in nodes further up the tree have any effect.  For example, if an @color directive is seen in node p, no @color or @nocolor directives are examined in any ancestor of p.

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

def scanAllDirectives(self,p,scripting=False,importing=False,reading=False,forcePythonSentinels=False):

    """Scan position p and p's ancestors looking for directives,
    setting corresponding atFile ivars.
    """

    # __pychecker__ = '--maxlines=400'
    # g.stat()

    c = self.c
    << Set ivars >>
    << Set path from @file node >>
    old = {}
    for p in p.self_and_parents_iter():
        theDict = g.get_directives_dict(p)
        << Test for @path >>
        << Test for @encoding >>
        << Test for @comment and @language >>
        << Test for @header and @noheader >>
        << Test for @lineending >>
        << Test for @pagewidth >>
        << Test for @tabwidth >>
        old.update(theDict)
    << Set current directory >>
    if not importing and not reading:
        # 5/19/04: don't override comment delims when reading!
        << Set comment strings from delims >>
    # For unit testing.
    return {
        "encoding"  : self.encoding,
        "language"  : self.language,
        "lineending": self.output_newline,
        "pagewidth" : self.page_width,
        "path"      : self.default_directory,
        "tabwidth"  : self.tab_width,
    }
#@+node:ekr.20041005105605.223:<< Set ivars >>
self.page_width = self.c.page_width
self.tab_width  = self.c.tab_width

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

if c.target_language:
    c.target_language = c.target_language.lower() # 6/20/05
delim1, delim2, delim3 = g.set_delims_from_language(c.target_language)
self.language = c.target_language

self.encoding = c.config.default_derived_file_encoding
self.output_newline = g.getOutputNewline(c=self.c) # Init from config settings.
#@-node:ekr.20041005105605.223:<< Set ivars >>
#@+node:ekr.20041005105605.224:<< Set path from @file node >> in scanDirectory in leoGlobals.py
# An absolute path in an @file node over-rides everything else.
# A relative path gets appended to the relative path by the open logic.

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

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

if theDir and len(theDir) > 0 and g.os_path_isabs(theDir):
    if g.os_path_exists(theDir):
        self.default_directory = theDir
    else: # 9/25/02
        self.default_directory = g.makeAllNonExistentDirectories(theDir,c=c)
        if not self.default_directory:
            self.error("Directory \"%s\" does not exist" % theDir)
#@-node:ekr.20041005105605.224:<< Set path from @file node >> in scanDirectory in leoGlobals.py
#@+node:ekr.20041005105605.225:<< Test for @path >> (atFile.scanAllDirectives)
# We set the current director to a path so future writes will go to that directory.

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

    path = theDict["path"]
    path = g.computeRelativePath(path)
    if path and len(path) > 0:
        base = g.getBaseDirectory(c) # returns "" on error.
        path = g.os_path_join(base,path)
        if g.os_path_isabs(path):
            << handle absolute path >>
        else:
            self.error("ignoring bad @path: %s" % path)
    else:
        self.error("ignoring empty @path")
#@+node:ekr.20041005105605.227:<< handle absolute path >>
# path is an absolute path.

if g.os_path_exists(path):
    self.default_directory = path
else:
    self.default_directory = g.makeAllNonExistentDirectories(path,c=c)
    if not self.default_directory:
        self.error("invalid @path: %s" % path)
#@-node:ekr.20041005105605.227:<< handle absolute path >>
#@-node:ekr.20041005105605.225:<< Test for @path >> (atFile.scanAllDirectives)
#@+node:ekr.20041005105605.228:<< Test for @encoding >>
if not old.has_key("encoding") and theDict.has_key("encoding"):

    e = g.scanAtEncodingDirective(theDict)
    if e:
        self.encoding = e
#@-node:ekr.20041005105605.228:<< Test for @encoding >>
#@+node:ekr.20041005105605.229:<< Test for @comment and @language >>
# 10/17/02: @language and @comment may coexist in @file trees.
# For this to be effective the @comment directive should follow the @language directive.

# 1/23/05: Any previous @language or @comment prevents processing up the tree.
# This code is now like the code in tangle.scanAlldirectives.

if old.has_key("comment") or old.has_key("language"):
    pass # Do nothing more.

elif theDict.has_key("comment"):
    z = theDict["comment"]
    delim1, delim2, delim3 = g.set_delims_from_string(z)

elif theDict.has_key("language"):
    z = theDict["language"]
    self.language,delim1,delim2,delim3 = g.set_language(z,0)
#@-node:ekr.20041005105605.229:<< Test for @comment and @language >>
#@+node:ekr.20041005105605.230:<< Test for @header and @noheader >>
# EKR: 10/10/02: perform the sames checks done by tangle.scanAllDirectives.
if theDict.has_key("header") and theDict.has_key("noheader"):
    g.es("conflicting @header and @noheader directives")
#@-node:ekr.20041005105605.230:<< Test for @header and @noheader >>
#@+node:ekr.20041005105605.231:<< Test for @lineending >>
if not old.has_key("lineending") and theDict.has_key("lineending"):

    lineending = g.scanAtLineendingDirective(theDict)
    if lineending:
        self.explicitLineEnding = True
        self.output_newline = lineending
        # g.trace(p.headString(),'lineending',repr(lineending))
#@-node:ekr.20041005105605.231:<< Test for @lineending >>
#@+node:ekr.20041005105605.232:<< Test for @pagewidth >>
if theDict.has_key("pagewidth") and not old.has_key("pagewidth"):

    w = g.scanAtPagewidthDirective(theDict,issue_error_flag=True)
    if w and w > 0:
        self.page_width = w
#@-node:ekr.20041005105605.232:<< Test for @pagewidth >>
#@+node:ekr.20041005105605.233:<< Test for @tabwidth >>
if theDict.has_key("tabwidth") and not old.has_key("tabwidth"):

    w = g.scanAtTabwidthDirective(theDict,issue_error_flag=True)
    if w and w != 0:
        self.tab_width = w
#@-node:ekr.20041005105605.233:<< Test for @tabwidth >>
#@+node:ekr.20041005105605.234:<< Set current directory >> (atFile.scanAllDirectives)
# This code is executed if no valid absolute path was specified in the @file node or in an @path directive.

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

if not self.default_directory and not scripting and not importing:
    # This should never happen: c.openDirectory should be a good last resort.
    g.trace()
    self.error("No absolute directory specified anywhere.")
    self.default_directory = ""
#@-node:ekr.20041005105605.234:<< Set current directory >> (atFile.scanAllDirectives)
#@+node:ekr.20041005105605.235:<< Set comment strings from delims >>
if forcePythonSentinels:
    # Force Python language.
    delim1,delim2,delim3 = g.set_delims_from_language("python")
    self.language = "python"

# Use single-line comments if we have a choice.
# delim1,delim2,delim3 now correspond to line,start,end
if delim1:
    self.startSentinelComment = delim1
    self.endSentinelComment = "" # Must not be None.
elif delim2 and delim3:
    self.startSentinelComment = delim2
    self.endSentinelComment = delim3
else: # Emergency!
    # assert(0)
    g.es("unknown language: using Python comment delimiters")
    g.es("c.target_language:",c.target_language)
    g.es('','delim1,delim2,delim3:','',delim1,'',delim2,'',delim3)
    self.startSentinelComment = "#" # This should never happen!
    self.endSentinelComment = ""

# g.trace(repr(self.startSentinelComment),repr(self.endSentinelComment))
#@-node:ekr.20041005105605.235:<< Set comment strings from delims >>
#@-node:ekr.20041005105605.222:atFile.scanAllDirectives
#@+node:ekr.20031218072017.1380:Directive utils...
#@+node:ekr.20031218072017.1381:the @language and @comment directives (leoUtils)
#@+node:ekr.20031218072017.1382:set_delims_from_language
# Returns a tuple (single,start,end) of comment delims

def set_delims_from_language(language):

    # g.trace(g.callers())

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

    """Returns (delim1, delim2, delim2), the delims following the @comment directive.

    This code can be called from @language logic, in which case s can point at @comment"""

    # Skip an optional @comment
    tag = "@comment"
    i = 0
    if g.match_word(s,i,tag):
        i += len(tag)

    count = 0 ; delims = [None, None, None]
    while count < 3 and i < len(s):
        i = j = g.skip_ws(s,i)
        while i < len(s) and not g.is_ws(s[i]) and not g.is_nl(s,i):
            i += 1
        if j == i: break
        delims[count] = s[j:i]
        count += 1

    # 'rr 09/25/02
    if count == 2: # delims[0] is always the single-line delim.
        delims[2] = delims[1]
        delims[1] = delims[0]
        delims[0] = None

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

    return delims[0], delims[1], delims[2]
#@-node:ekr.20031218072017.1383:set_delims_from_string
#@+node:ekr.20031218072017.1384:set_language
def set_language(s,i,issue_errors_flag=False):

    """Scan the @language directive that appears at s[i:].

    The @language may have been stripped away.

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

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

    if issue_errors_flag:
        g.es("ignoring:",g.get_line(s,i))

    return None, None, None, None,
#@-node:ekr.20031218072017.1384:set_language
#@-node:ekr.20031218072017.1381:the @language and @comment directives (leoUtils)
#@+node:EKR.20040504150046.4:g.comment_delims_from_extension
def comment_delims_from_extension(filename):

    """
    Return the comment delims corresponding to the filename's extension.

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

    >>> g.comment_delims_from_extension(".c")
    ('//', '/*', '*/')

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

    """

    root, ext = os.path.splitext(filename)
    if ext == '.tmp':
        root, ext = os.path.splitext(root)

    language = g.app.extension_dict.get(ext[1:])
    if ext:

        return g.set_delims_from_language(language)
    else:
        g.trace("unknown extension %s" % ext)
        return None,None,None
#@-node:EKR.20040504150046.4:g.comment_delims_from_extension
#@+node:ekr.20071109165315:g.computeRelativePath
def computeRelativePath (path):

    if len(path) > 2 and (
        (path[0]=='<' and path[-1] == '>') or
        (path[0]=='"' and path[-1] == '"') or
        (path[0]=="'" and path[-1] == "'")
    ):
        path = path[1:-1].strip()

    # 11/14/02: we want a _relative_ path, not an absolute path.
    # path = g.os_path_join(g.app.loadDir,path)

    return path
#@-node:ekr.20071109165315:g.computeRelativePath
#@+node:ekr.20031218072017.1385:g.findReference
@ We search the descendents of v looking for the definition node matching name.
There should be exactly one such node (descendents of other definition nodes are not searched).
@c

def findReference(c,name,root):

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

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

def get_directives_dict(p,root=None):

    """Scans root for @directives found in globalDirectiveList.

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

    if root: root_node = root[0]
    theDict = {}

    # The headline has higher precedence because it is more visible.
    for kind,s in (
        ('body',p.v.t.headString),
        ('head',p.v.t.bodyString),
    ):
        i = 0 ; n = len(s)
        while i < n:
            if s[i] == '@' and i+1 < n:
                << set theDict for @ directives >>
            elif kind == 'body' and root and g.match(s,i,"<<"):
                << set theDict["root"] for noweb * chunks >>
            i = g.skip_line(s,i)
    return theDict
#@+node:ekr.20031218072017.1261:<< set theDict for @ directives >>
j = g.skip_c_id(s,i+1)
word = s[i+1:j]

global globalDirectiveList

if word in globalDirectiveList:
    if theDict.has_key(word):
        # Ignore second value.
        pass
        # g.es("warning: conflicting values for",word,color="blue")
    else:
        # theDict [word] = i
        k = g.skip_line(s,j)
        theDict[word] = s[j:k].strip()
#@nonl
#@-node:ekr.20031218072017.1261:<< set theDict for @ directives >>
#@+node:ekr.20031218072017.1262:<< set theDict["root"] for noweb * chunks >>
@ The following looks for chunk definitions of the form < < * > > =. If found, we take this to be equivalent to @root filename if the headline has the form @root filename.
@c

i = g.skip_ws(s,i+2)

if i < n and s[i] == '*' :
    i = g.skip_ws(s,i+1) # Skip the '*'
    if g.match(s,i,">>="):
        # < < * > > = implies that @root should appear in the headline.
        i += 3
        if root_node:
            theDict["root"]=0 # value not immportant
        else:
            g.es('',g.angleBrackets("*") + "= requires @root in the headline")
#@-node:ekr.20031218072017.1262:<< set theDict["root"] for noweb * chunks >>
#@-node:ekr.20031218072017.1260:g.get_directives_dict
#@+node:ekr.20031218072017.1387:g.scanAtEncodingDirective
def scanAtEncodingDirective(theDict):

    """Scan the @encoding directive at s[theDict["encoding"]:].

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

    encoding = theDict.get('encoding')
    if not encoding:
        return None

    if g.isValidEncoding(encoding):
        # g.trace(encoding)
        return encoding
    else:
        g.es("invalid @encoding:",encoding,color="red")
        return None
#@-node:ekr.20031218072017.1387:g.scanAtEncodingDirective
#@+node:ekr.20031218072017.1388:g.scanAtLineendingDirective
def scanAtLineendingDirective(theDict):

    """Scan the @lineending directive at s[theDict["lineending"]:].

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

    e = theDict.get('lineending')

    if e in ("cr","crlf","lf","nl","platform"):
        lineending = g.getOutputNewline(name=e)
        # g.trace(e,lineending)
        return lineending
    else:
        # g.es("invalid @lineending directive:",e,color="red")
        return None
#@-node:ekr.20031218072017.1388:g.scanAtLineendingDirective
#@+node:ekr.20031218072017.1389:g.scanAtPagewidthDirective
def scanAtPagewidthDirective(theDict,issue_error_flag=False):

    """Scan the @pagewidth directive at s[theDict["pagewidth"]:].

    Returns the value of the width or None if the width is invalid.
    """

    s = theDict.get('pagewidth')
    i, val = g.skip_long(s,0)

    if val != None and val > 0:
        # g.trace(val)
        return val
    else:
        if issue_error_flag:
            g.es("ignoring",s,color="red")
        return None
#@-node:ekr.20031218072017.1389:g.scanAtPagewidthDirective
#@+node:ekr.20031218072017.3154:g.scanAtRootOptions
def scanAtRootOptions (s,i,err_flag=False):

    # The @root has been eaten when called from tangle.scanAllDirectives.
    if g.match(s,i,"@root"):
        i += len("@root")
        i = g.skip_ws(s,i)

    mode = None 
    while g.match(s,i,'-'):
        << scan another @root option >>

    if mode == None:
        doc = app.config.at_root_bodies_start_in_doc_mode
        mode = g.choose(doc,"doc","code")

    return i,mode
#@+node:ekr.20031218072017.3155:<< scan another @root option >>
i += 1 ; err = -1

if g.match_word(s,i,"code"): # Just match the prefix.
    if not mode: mode = "code"
    elif err_flag: g.es("modes conflict in:",g.get_line(s,i))
elif g.match(s,i,"doc"): # Just match the prefix.
    if not mode: mode = "doc"
    elif err_flag: g.es("modes conflict in:",g.get_line(s,i))
else:
    err = i-1

# Scan to the next minus sign.
while i < len(s) and s[i] not in (' ','\t','-'):
    i += 1

if err > -1 and err_flag:
    z_opt = s[err:i]
    z_line = g.get_line(s,i)
    g.es("unknown option:",z_opt,"in",z_line)
#@-node:ekr.20031218072017.3155:<< scan another @root option >>
#@-node:ekr.20031218072017.3154:g.scanAtRootOptions
#@+node:ekr.20031218072017.1390:g.scanAtTabwidthDirective
def scanAtTabwidthDirective(theDict,issue_error_flag=False):

    """Scan the @tabwidth directive at s[theDict["tabwidth"]:].

    Returns the value of the width or None if the width is invalid.
    """

    s = theDict.get('tabwidth')
    junk,val = g.skip_long(s,0)

    if val != None and val != 0:
        # g.trace(val)
        return val
    else:
        if issue_error_flag:
            g.es("ignoring",s,color="red")
        return None
#@-node:ekr.20031218072017.1390:g.scanAtTabwidthDirective
#@+node:ekr.20070302160802:g.scanColorDirectives
def scanColorDirectives(c,p):

    '''Return the language in effect at position p.'''

    if c is None: return # c may be None for testing.

    language = c.target_language and c.target_language.lower() or 'python'

    p = p.copy()
    for p in p.self_and_parents_iter():
        d = g.get_directives_dict(p)
        z = d.get('language')
        if z is not None:
            language,junk,junk,junk = g.set_language(z,0)
            return language

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

def scanDirectives(c,p=None):

    """Scan vnode v and v's ancestors looking for directives.

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

    if p is None:
        p = c.currentPosition()

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

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

    # g.trace('tabwidth',tab_width)

    return {
        "delims"    : (delim1,delim2,delim3),
        "encoding"  : encoding,
        "language"  : language,
        "lineending": lineending,
        "pagewidth" : page_width,
        "path"      : path,
        "tabwidth"  : tab_width,
        "pluginsList": pluginsList,
        "wrap"      : wrap }
#@+node:ekr.20031218072017.1392:<< Set local vars >>
page_width = c.page_width
tab_width  = c.tab_width
language = c.target_language
if c.target_language:
    c.target_language = c.target_language.lower()
delim1, delim2, delim3 = g.set_delims_from_language(c.target_language)
path = None
encoding = None # 2/25/03: This must be none so that the caller can set a proper default.
lineending = g.getOutputNewline(c=c) # Init from config settings.
wrap = c.config.getBool("body_pane_wraps")
#@-node:ekr.20031218072017.1392:<< Set local vars >>
#@+node:ekr.20031218072017.1393:<< Test for @comment and @language >>
# 1/23/05: Any previous @language or @comment prevents processing up the tree.
# This code is now like the code in tangle.scanAlldirectives.

if old.has_key("comment") or old.has_key("language"):
    pass

elif theDict.has_key("comment"):
    z = theDict["comment"]
    delim1,delim2,delim3 = g.set_delims_from_string(z)

elif theDict.has_key("language"):
    z = theDict["language"]
    language,delim1,delim2,delim3 = g.set_language(z,0)
#@-node:ekr.20031218072017.1393:<< Test for @comment and @language >>
#@+node:ekr.20031218072017.1394:<< Test for @encoding >>
if not old.has_key("encoding") and theDict.has_key("encoding"):

    e = g.scanAtEncodingDirective(theDict)
    if e:
        encoding = e
#@-node:ekr.20031218072017.1394:<< Test for @encoding >>
#@+node:ekr.20031218072017.1395:<< Test for @lineending >>
if not old.has_key("lineending") and theDict.has_key("lineending"):

    e = g.scanAtLineendingDirective(theDict)
    if e:
        lineending = e
#@-node:ekr.20031218072017.1395:<< Test for @lineending >>
#@+node:ekr.20031218072017.1396:<< Test for @pagewidth >>
if theDict.has_key("pagewidth") and not old.has_key("pagewidth"):

    w = g.scanAtPagewidthDirective(theDict)
    if w and w > 0:
        page_width = w
#@-node:ekr.20031218072017.1396:<< Test for @pagewidth >>
#@+node:ekr.20031218072017.1397:<< Test for @path >> (g.scanDirectives)
if not path and not old.has_key("path") and theDict.has_key("path"):

    path = theDict["path"]
    path = g.computeRelativePath(path)

    if path and len(path) > 0:
        base = g.getBaseDirectory(c) # returns "" on error.
        path = g.os_path_join(base,path)
#@-node:ekr.20031218072017.1397:<< Test for @path >> (g.scanDirectives)
#@+node:ekr.20031218072017.1399:<< Test for @tabwidth >>
if theDict.has_key("tabwidth") and not old.has_key("tabwidth"):

    w = g.scanAtTabwidthDirective(theDict)
    if w and w != 0:
        tab_width = w
#@-node:ekr.20031218072017.1399:<< Test for @tabwidth >>
#@+node:ekr.20031218072017.1400:<< Test for @wrap and @nowrap >>
if not old.has_key("wrap") and not old.has_key("nowrap"):

    if theDict.has_key("wrap"):
        wrap = True
    elif theDict.has_key("nowrap"):
        wrap = False
#@-node:ekr.20031218072017.1400:<< Test for @wrap and @nowrap >>
#@-node:ekr.20031218072017.1391:g.scanDirectives
#@+node:ekr.20040715155607:g.scanForAtIgnore
def scanForAtIgnore(c,p):

    """Scan position p and its ancestors looking for @ignore directives."""

    if g.app.unitTesting:
        return False # For unit tests.

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

    return False
#@-node:ekr.20040715155607:g.scanForAtIgnore
#@+node:ekr.20040712084911.1:g.scanForAtLanguage
def scanForAtLanguage(c,p):

    """Scan position p and p's ancestors looking only for @language and @ignore directives.

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

    # Unlike the code in x.scanAllDirectives, this code ignores @comment directives.

    if c and p:
        for p in p.self_and_parents_iter():
            d = g.get_directives_dict(p)
            if d.has_key("language"):
                z = d["language"]
                language,delim1,delim2,delim3 = g.set_language(z,0)
                return language

    return c.target_language
#@-node:ekr.20040712084911.1:g.scanForAtLanguage
#@+node:ekr.20041123094807:g.scanForAtSettings
def scanForAtSettings(p):

    """Scan position p and its ancestors looking for @settings nodes."""

    for p in p.self_and_parents_iter():
        h = p.headString()
        h = g.app.config.canonicalizeSettingName(h)
        if h.startswith("@settings"):
            return True

    return False
#@-node:ekr.20041123094807:g.scanForAtSettings
#@+node:ekr.20031218072017.1386:getOutputNewline
def getOutputNewline (c=None,name=None):

    '''Convert the name of a line ending to the line ending itself.

    Priority:
    - Use name if name given
    - Use c.config.output_newline if c given,
    - Otherwise use g.app.config.output_newline.'''

    if name: s = name
    elif c:  s = c.config.output_newline
    else:    s = app.config.output_newline

    if not s: s = ''
    s = s.lower()
    if s in ( "nl","lf"): s = '\n'
    elif s == "cr": s = '\r'
    elif s == "platform": s = os.linesep  # 12/2/03: emakital
    elif s == "crlf": s = "\r\n"
    else: s = '\n' # Default for erroneous values.
    # g.trace(c,name,c.config.output_newline,'returns',repr(s))
    return s
#@-node:ekr.20031218072017.1386:getOutputNewline
#@-node:ekr.20031218072017.1380:Directive utils...
#@-node:ekr.20080322060140.1:Fixed @lineending botch
#@+node:ekr.20080321081209.3:Fixed undo problem with mutliple editors
@nocolor

http://mail.google.com/mail/#inbox/11899019a43f74ca

On Thu, 20 Mar 2008 04:08:33 -0700 (PDT) bobjack <bobjack@post.com> wrote:

When you have two editors open on the same node most actions show up in both
editors, however, if you do an undo the change only show up in the selected
editor. (The change shows up in the other editor when you select it).
#@-node:ekr.20080321081209.3:Fixed undo problem with mutliple editors
#@+node:ekr.20080318081653.2:Write dirty bit if write fails
#@+node:ekr.20080318081653.3:Report
@nocolor

http://groups.google.com/group/leo-editor/browse_thread/thread/71822f48720e71bc/6e0fd1c72fa39817#6e0fd1c72fa39817

I'm programming in an interpreted language that causes me to
occasionally issue a Leo save operation which results in my @thin
source files to fail to write because the interpreter has locked the
files.  Unfortunately, Leo clears the dirty flag.  Here are the
messages in the Log:

errors writing: c:\_Proj\File.abc
can not create: read only: c:\_Proj\File.abc
Writing erroneous: @thin File.abc
saved: TuLs.leo

Is there an easy way to patch the Leo code to keep the dirty flag set
if the write fails?

Also, what is meant by the message "Writing erroneous: @thin
File.abc"?

The problem is easy to reproduce  by setting a file to readonly.
#@nonl
#@-node:ekr.20080318081653.3:Report
#@+node:ekr.20080324073625.1:Report 2
@nocolor

The output in my original post occured when I manually set one of my
derived files to ReadOnly.

EKR: Fixed as follows:

- openFileForWritingHelper now returns False if there is a problem.
- openFileForWriting sets dirty bit if openFileForWritingHelper returns False.

The following output occurred when one of
my files was locked by the interpreter I use.  The write was triggered
when I performed a file save from VIM using the "Open With" pluggin.
I hope it helps with your analysis of the problem.

creating:  File.abc
updated from: File.abc
exception removing: c:\_Proj\File.abc
Traceback (most recent call last):
 File "C:\Program Files\_Progs\Leo\src\leoAtFile.py", line 4975, in
remove
   os.remove(fileName)
WindowsError: [Error 32] The process cannot access the file because it
is being used by another process: u'c:\\_Proj\\File.abc
saved: TuLs.leo

EKR: Fixed as follows:

- replaceTargetFileIfDifferent calls self.root.setDirty() on errors.
#@nonl
#@-node:ekr.20080324073625.1:Report 2
#@+node:ekr.20080321081209.6:Reply
@nocolor

> I'm programming in an interpreted language that causes me to
> occasionally issue a Leo save operation which results in my @thin
> source files to fail to write because the interpreter has locked the
> files.  Unfortunately, Leo clears the dirty flag.

This turns out to be tricky to fix.  I've been thinking about this at
odd moments for the last several days.  My present plan:

1. Do several ugly fixes for now, and include those fixes in 4.4.8
final.  These should be enough to fix the recently reported problems.
Their great virtue is that they will have no impact on any other write
code.

2. Experiment with a refactored approach in the 4.5 code cycle. The
obvious refactoring is to create two new write helpers:  at.preWrite
and at.postWrite.  However, these methods might require many special
cases.  If there are too many, we will, in effect, be back where we
started, except that the special cases will be even more difficult to
understand.

There are interesting refactoring issues here:

One problem with 2 is that refactoring will require many unit tests
that I don't have, and these unit tests involve the file attributes.
It looks like these tests will require a framework for simulating
files with various attributes.  So it's a project.

Another problem with 2 is that it may be a trick to determine what
should be unit tested.  Working backward from the present code will
not be fun.

A third problem with 2 is that the refactoring itself may be a dubious
design.  Just because there is common code does not guarantee that
segregating the code into a separate method will be a good idea.

These second and third problems are other ways of saying that it's not
clear how many special cases the at.preWrite and at.postWrite methods
will have.

OTOH, the problem with *not* doing 2 is that it is, in fact, difficult
to be sure that the pre and post conditions of all kinds of writes are
satisfied.

In short, point 2 can not be done at this point in the 4.4.8 release
cycle. 
#@nonl
#@-node:ekr.20080321081209.6:Reply
#@+node:ekr.20031218072017.2834:save (commands)
def save (self,event=None):

    '''Save a Leo outline to a file.'''

    c = self ; w = g.app.gui.get_focus(c)

    if g.app.disableSave:
        g.es("save commands disabled",color="purple")
        return

    # Make sure we never pass None to the ctor.
    if not c.mFileName:
        c.frame.title = ""
        c.mFileName = ""

    c.beginUpdate()
    try:
        if c.mFileName:
            # Calls c.setChanged(False) if no error.
            c.fileCommands.save(c.mFileName)
        else:
            fileName = ''.join(c.k.givenArgs) or g.app.gui.runSaveFileDialog(
                initialfile = c.mFileName,
                title="Save",
                filetypes=[("Leo files", "*.leo")],
                defaultextension=".leo")
            c.bringToFront()

            if fileName:
                # Don't change mFileName until the dialog has suceeded.
                c.mFileName = g.ensure_extension(fileName, ".leo")
                c.frame.title = c.mFileName
                c.frame.setTitle(g.computeWindowTitle(c.mFileName))
                c.frame.openDirectory = g.os_path_dirname(c.mFileName) # Bug fix in 4.4b2.
                c.fileCommands.save(c.mFileName)
                c.updateRecentFiles(c.mFileName)
    finally:
        c.endUpdate()
        c.widgetWantsFocus(w)
#@nonl
#@-node:ekr.20031218072017.2834:save (commands)
#@+node:ekr.20031218072017.1720:save (fileCommands)
def save(self,fileName):

    c = self.c ; v = c.currentVnode()

    # New in 4.2.  Return ok flag so shutdown logic knows if all went well.
    ok = g.doHook("save1",c=c,p=v,v=v,fileName=fileName)
    # redraw_flag = g.app.gui.guiName() == 'tkinter'
    if ok is None:
        c.beginUpdate()
        try:
            c.endEditing()# Set the current headline text.
            self.setDefaultDirectoryForNewFiles(fileName)
            ok = self.write_Leo_file(fileName,False) # outlineOnlyFlag
            self.putSavedMessage(fileName)
            if ok:
                c.setChanged(False) # Clears all dirty bits.
                if c.config.save_clears_undo_buffer:
                    g.es("clearing undo")
                    c.undoer.clearUndoState()
        finally:
            c.endUpdate() # We must redraw in order to clear dirty node icons.
    g.doHook("save2",c=c,p=v,v=v,fileName=fileName)
    return ok
#@-node:ekr.20031218072017.1720:save (fileCommands)
#@+node:ekr.20031218072017.3046:write_Leo_file
def write_Leo_file(self,fileName,outlineOnlyFlag,toString=False,toOPML=False):

    c = self.c
    self.putCount = 0
    self.toString = toString
    theActualFile = None
    toZip = False
    atOk = True

    if not outlineOnlyFlag or toOPML:
        # Update .leoRecentFiles.txt if possible.
        g.app.config.writeRecentFilesFile(c)
        << write all @file nodes >>
    << return if the .leo file is read-only >>
    try:
        << create backup file >>
        self.mFileName = fileName
        if toOPML:
            << ensure that filename ends with .opml >>
        self.outputFile = cStringIO.StringIO()
        << create theActualFile >>
        # t1 = time.clock()
        if toOPML:
            self.putToOPML()
        else:
            # An important optimization: we have already assign the file indices.
            self.putLeoFile()
        # t2 = time.clock()
        s = self.outputFile.getvalue()
        # g.trace(self.leo_file_encoding)
        if toZip:
            self.writeZipFile(s)
        elif toString:
            # For support of chapters plugin.
            g.app.write_Leo_file_string = s
        else:
            theActualFile.write(s)
            theActualFile.close()
            << delete backup file >>
            # t3 = time.clock()
            # g.es_print('len',len(s),'putCount',self.putCount) # 'put',t2-t1,'write&close',t3-t2)
        self.outputFile = None
        self.toString = False
        return atOk
    except Exception:
        g.es("exception writing:",fileName)
        g.es_exception(full=True)
        if theActualFile: theActualFile.close()
        self.outputFile = None
        if backupName:
            << delete fileName >>
            << rename backupName to fileName >>
        self.toString = False
        return False

write_LEO_file = write_Leo_file # For compatibility with old plugins.
#@+node:ekr.20040324080359:<< write all @file nodes >>
try:
    # Write all @file nodes and set orphan bits.
    # An important optimization: we have already assign the file indices.
    changedFiles,atOk = c.atFileCommands.writeAll()
except Exception:
    g.es_error("exception writing derived files")
    g.es_exception()
    return False
#@-node:ekr.20040324080359:<< write all @file nodes >>
#@+node:ekr.20040324080359.1:<< return if the .leo file is read-only >>
# self.read_only is not valid for Save As and Save To commands.

if g.os_path_exists(fileName):
    try:
        if not os.access(fileName,os.W_OK):
            g.es("can not write: read only:",fileName,color="red")
            return False
    except Exception:
        pass # os.access() may not exist on all platforms.
#@-node:ekr.20040324080359.1:<< return if the .leo file is read-only >>
#@+node:ekr.20031218072017.3047:<< create backup file >>
backupName = None

# rename fileName to fileName.bak if fileName exists.
if not toString and g.os_path_exists(fileName):
    backupName = g.os_path_join(g.app.loadDir,fileName)
    backupName = fileName + ".bak"
    if g.os_path_exists(backupName):
        g.utils_remove(backupName)
    ok = g.utils_rename(c,fileName,backupName)
    if not ok:
        if self.read_only:
            g.es("read only",color="red")
        return False
#@nonl
#@-node:ekr.20031218072017.3047:<< create backup file >>
#@+node:ekr.20060919070145:<< ensure that filename ends with .opml >>
if not self.mFileName.endswith('opml'):
    self.mFileName = self.mFileName + '.opml'
fileName = self.mFileName
#@nonl
#@-node:ekr.20060919070145:<< ensure that filename ends with .opml >>
#@+node:ekr.20060929103258:<< create theActualFile >>
if toString:
    theActualFile = None
elif c.isZipped:
    self.toString = toString = True
    theActualFile = None
    toZip = True
else:
    theActualFile = open(fileName, 'wb')
#@-node:ekr.20060929103258:<< create theActualFile >>
#@+node:ekr.20031218072017.3048:<< delete backup file >>
if backupName and g.os_path_exists(backupName):

    self.deleteFileWithMessage(backupName,'backup')
#@-node:ekr.20031218072017.3048:<< delete backup file >>
#@+node:ekr.20050405103712:<< delete fileName >>
if fileName and g.os_path_exists(fileName):

    self.deleteFileWithMessage(fileName,'')
#@-node:ekr.20050405103712:<< delete fileName >>
#@+node:ekr.20050405103712.1:<< rename backupName to fileName >>
if backupName:
    g.es("restoring",fileName,"from",backupName)
    g.utils_rename(c,backupName,fileName)
#@-node:ekr.20050405103712.1:<< rename backupName to fileName >>
#@+node:ekr.20070412095520:writeZipFile
def writeZipFile (self,s):

    # The name of the file in the archive.
    contentsName = g.toEncodedString(
        g.shortFileName(self.mFileName),
        self.leo_file_encoding,reportErrors=True)

    # The name of the archive itself.
    fileName = g.toEncodedString(
        self.mFileName,
        self.leo_file_encoding,reportErrors=True)

    # Write the archive.
    theFile = zipfile.ZipFile(fileName,'w',zipfile.ZIP_DEFLATED)
    theFile.writestr(contentsName,s)
    theFile.close()
#@-node:ekr.20070412095520:writeZipFile
#@-node:ekr.20031218072017.3046:write_Leo_file
#@+node:ekr.20041005105605.147:writeAll (atFile)
def writeAll(self,
    writeAtFileNodesFlag=False,
    writeDirtyAtFileNodesFlag=False,
    toString=False,
):

    """Write @file nodes in all or part of the outline"""

    at = self ; c = at.c
    writtenFiles = [] # Files that might be written again.
    mustAutoSave = False ; atOk = True

    if writeAtFileNodesFlag:
        # Write all nodes in the selected tree.
        p = c.currentPosition()
        after = p.nodeAfterTree()
    else:
        # Write dirty nodes in the entire outline.
        p =  c.rootPosition()
        after = c.nullPosition()

    << Clear all orphan bits >>
    atOk = True
    while p and p != after:
        if p.isAnyAtFileNode() or p.isAtIgnoreNode():
            << handle v's tree >>
            p.moveToNodeAfterTree()
        else:
            p.moveToThreadNext()

    << say the command is finished >>
    return mustAutoSave,atOk
#@+node:ekr.20041005105605.148:<< Clear all orphan bits >>
@ We must clear these bits because they may have been set on a previous write.
Calls to atFile::write may set the orphan bits in @file nodes.
If so, write_Leo_file will write the entire @file tree.
@c

for v2 in p.self_and_subtree_iter():
    v2.clearOrphan()
#@-node:ekr.20041005105605.148:<< Clear all orphan bits >>
#@+node:ekr.20041005105605.149:<< handle v's tree >>
if p.v.isDirty() or writeAtFileNodesFlag or p.v.t in writtenFiles:

    at.fileChangedFlag = False
    autoSave = False

    # Tricky: @ignore not recognised in @silentfile nodes.
    if p.isAtAsisFileNode():
        at.asisWrite(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True
    elif p.isAtIgnoreNode():
        pass
    elif p.isAtAutoNode():
        at.writeOneAtAutoNode(p,toString=toString,force=False)
        writtenFiles.append(p.v.t)
    elif p.isAtNorefFileNode():
        at.norefWrite(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True
    elif p.isAtNoSentFileNode():
        at.write(p,nosentinels=True,toString=toString)
        writtenFiles.append(p.v.t) # No need for autosave
    elif p.isAtThinFileNode():
        at.write(p,thinFile=True,toString=toString)
        writtenFiles.append(p.v.t) # No need for autosave.
    elif p.isAtFileNode():
        at.write(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True

    if at.errors: atOk = False

    if at.fileChangedFlag and autoSave: # Set by replaceTargetFileIfDifferent.
        mustAutoSave = True
#@-node:ekr.20041005105605.149:<< handle v's tree >>
#@+node:ekr.20041005105605.150:<< say the command is finished >>
if writeAtFileNodesFlag or writeDirtyAtFileNodesFlag:
    if len(writtenFiles) > 0:
        g.es("finished")
    elif writeAtFileNodesFlag:
        g.es("no @file nodes in the selected tree")
    else:
        g.es("no dirty @file nodes")
#@-node:ekr.20041005105605.150:<< say the command is finished >>
#@-node:ekr.20041005105605.147:writeAll (atFile)
#@+node:ekr.20041005105605.144:write
# This is the entry point to the write code.  root should be an @file vnode.

def write(self,root,
    nosentinels=False,
    thinFile=False,
    scriptWrite=False,
    toString=False,
    write_strips_blank_lines=None,
):

    """Write a 4.x derived file."""

    at = self ; c = at.c
    c.endEditing() # Capture the current headline.

    << set at.targetFileName >>
    at.initWriteIvars(root,at.targetFileName,
        nosentinels=nosentinels,thinFile=thinFile,
        scriptWrite=scriptWrite,toString=toString,
        write_strips_blank_lines=write_strips_blank_lines)
    if not at.openFileForWriting(root,at.targetFileName,toString):
        # openFileForWriting calls root.setDirty() if there are errors.
        return

    try:
        at.writeOpenFile(root,nosentinels=nosentinels,toString=toString)
        if toString:
            at.closeWriteFile() # sets self.stringOutput
            # Major bug: failure to clear this wipes out headlines!
            # Minor bug: sometimes this causes slight problems...
            at.root.v.t.tnodeList = []
            at.root.v.t._p_changed = True
        else:
            at.closeWriteFile()
            << set dirty and orphan bits on error >>
    except Exception:
        if toString:
            at.exception("exception preprocessing script")
            at.root.v.t.tnodeList = []
            at.root.v.t._p_changed = True
        else:
            at.writeException() # Sets dirty and orphan bits.
#@+node:ekr.20041005105605.145:<< set at.targetFileName >>
if toString:
    at.targetFileName = "<string-file>"
elif nosentinels:
    at.targetFileName = root.atNoSentFileNodeName()
elif thinFile:
    at.targetFileName = root.atThinFileNodeName()
else:
    at.targetFileName = root.atFileNodeName()
#@-node:ekr.20041005105605.145:<< set at.targetFileName >>
#@+node:ekr.20041005105605.146:<< set dirty and orphan bits on error >>
# Setting the orphan and dirty flags tells Leo to write the tree..

if at.errors > 0 or at.root.isOrphan():
    root.setOrphan()
    root.setDirty() # Make _sure_ we try to rewrite this file.
    ### os.remove(at.outputFileName) # Delete the temp file.
    self.remove(at.outputFileName) # Delete the temp file.
    g.es("not written:",at.outputFileName)
else:
    at.replaceTargetFileIfDifferent(root) # Sets/clears dirty and orphan bits.
#@-node:ekr.20041005105605.146:<< set dirty and orphan bits on error >>
#@-node:ekr.20041005105605.144:write
#@+node:ekr.20050104131929.1:atFile.rename
<< about os.rename >>

def rename (self,src,dst,mode=None,verbose=True):

    '''remove dst if it exists, then rename src to dst.

    Change the mode of the renamed file if mode is given.

    Return True if all went well.'''

    c = self.c
    head,junk=g.os_path_split(dst)
    if head and len(head) > 0:
        g.makeAllNonExistentDirectories(head,c=c)

    if g.os_path_exists(dst):
        if not self.remove(dst,verbose=verbose):
            return False

    try:
        os.rename(src,dst)
        if mode != None:
            self.chmod(dst,mode)
        return True
    except Exception:
        if verbose:
            self.error("exception renaming: %s to: %s" % (
                self.outputFileName,self.targetFileName))
            g.es_exception()
        return False
#@+node:ekr.20050104131929.2:<< about os.rename >>
@ Here is the Python 2.4 documentation for rename (same as Python 2.3)

Rename the file or directory src to dst.  If dst is a directory, OSError will be raised.

On Unix, if dst exists and is a file, it will be removed silently if the user
has permission. The operation may fail on some Unix flavors if src and dst are
on different filesystems. If successful, the renaming will be an atomic
operation (this is a POSIX requirement).

On Windows, if dst already exists, OSError will be raised even if it is a file;
there may be no way to implement an atomic rename when dst names an existing
file.
#@-node:ekr.20050104131929.2:<< about os.rename >>
#@-node:ekr.20050104131929.1:atFile.rename
#@+node:ekr.20050104132018:atFile.remove
def remove (self,fileName,verbose=True):

    try:
        os.remove(fileName)
        return True
    except Exception:
        if verbose:
            self.error("exception removing: %s" % fileName)
            g.es_exception()
        return False
#@-node:ekr.20050104132018:atFile.remove
#@+node:ekr.20041005105605.218:writeException
def writeException (self,root=None):

    g.es("exception writing:",self.targetFileName,color="red")
    g.es_exception()

    if self.outputFile:
        self.outputFile.flush()
        self.outputFile.close()
        self.outputFile = None

    if self.outputFileName:
        # try: # Just delete the temp file.
            # os.remove(self.outputFileName)
        # except Exception:
            # g.es("exception deleting:",self.outputFileName,color="red")
            # g.es_exception()
        self.remove(self.outputFileName)

    if root:
        # Make sure we try to rewrite this file.
        root.setOrphan()
        root.setDirty()
#@-node:ekr.20041005105605.218:writeException
#@+node:ekr.20041005105605.135:closeWriteFile
# 4.0: Don't use newline-pending logic.

def closeWriteFile (self):

    at = self

    if at.outputFile:
        at.outputFile.flush()
        if self.toString:
            self.stringOutput = self.outputFile.get()
        at.outputFile.close()
        at.outputFile = None
#@-node:ekr.20041005105605.135:closeWriteFile
#@+node:ekr.20041005105605.142:openFileForWriting & openFileForWritingHelper
def openFileForWriting (self,root,fileName,toString):

    at = self
    at.outputFile = None

    if toString:
        at.shortFileName = g.shortFileName(fileName)
        at.outputFileName = "<string: %s>" % at.shortFileName
        at.outputFile = g.fileLikeObject()
    else:
        ok = at.openFileForWritingHelper(fileName)

        # New in Leo 4.4.8: set dirty bit if there are errors.
        if not ok: at.outputFile = None

    # New in 4.3 b2: root may be none when writing from a string.
    if root:
        if at.outputFile:
            root.clearOrphan()
        else:
            root.setOrphan()
            root.setDirty()

    return at.outputFile is not None
#@+node:ekr.20041005105605.143:openFileForWritingHelper
def openFileForWritingHelper (self,fileName):

    '''Open the file and return True if all went well.'''

    at = self ; c = at.c

    try:
        at.shortFileName = g.shortFileName(fileName)
        fileName = g.os_path_join(at.default_directory,fileName)
        at.targetFileName = g.os_path_normpath(fileName)
        path = g.os_path_dirname(at.targetFileName)
        if not path or not g.os_path_exists(path):
            if path:
                path = g.makeAllNonExistentDirectories(path,c=c)
            if not path or not g.os_path_exists(path):
                path = g.os_path_dirname(at.targetFileName)
                at.writeError("path does not exist: " + path)
                return False
    except Exception:
        at.exception("exception creating path: %s" % repr(path))
        g.es_exception()
        return False

    if g.os_path_exists(at.targetFileName):
        try:
            if not os.access(at.targetFileName,os.W_OK):
                at.writeError("can not open: read only: " + at.targetFileName)
                return False
        except AttributeError:
            pass # os.access() may not exist on all platforms.

    try:
        at.outputFileName = at.targetFileName + ".tmp"
        at.outputFile = self.openForWrite(at.outputFileName,'wb') # bwm
        if not at.outputFile:
            at.writeError("can not create " + at.outputFileName)
            return False
    except Exception:
        at.exception("exception creating:" + at.outputFileName)
        return False

    return True
#@-node:ekr.20041005105605.143:openFileForWritingHelper
#@-node:ekr.20041005105605.142:openFileForWriting & openFileForWritingHelper
#@+node:ekr.20031218072017.1863:putVnode (3.x and 4.x)
def putVnode (self,p,isIgnore=False):

    """Write a <v> element corresponding to a vnode."""

    fc = self ; c = fc.c ; v = p.v
    # Not writing @auto nodes is way too dangerous.
    isAuto = p.isAtAutoNode() and p.atAutoNodeName().strip()
    isThin = p.isAtThinFileNode()
    isOrphan = p.isOrphan()
    if not isIgnore: isIgnore = p.isAtIgnoreNode()

    # forceWrite = isIgnore or not isThin or (isThin and isOrphan)
    if isIgnore: forceWrite = True      # Always write full @ignore trees.
    elif isAuto: forceWrite = False     # Never write non-ignored @auto trees.
    elif isThin: forceWrite = isOrphan  # Only write orphan @thin trees.
    else:        forceWrite = True      # Write all other @file trees.

    << Set gnx = tnode index >>
    attrs = []
    << Append attribute bits to attrs >>
    << Append tnodeList and unKnownAttributes to attrs >>
    attrs = ''.join(attrs)
    v_head = '<v t="%s"%s><vh>%s</vh>' % (gnx,attrs,xml.sax.saxutils.escape(p.v.headString()or''))
    # The string catentation is faster than repeated calls to fc.put.
    if not self.usingClipboard:
        << issue informational messages >>
    # New in 4.2: don't write child nodes of @file-thin trees (except when writing to clipboard)
    if p.hasChildren() and (forceWrite or self.usingClipboard):
        fc.put('%s\n' % v_head)
        # This optimization eliminates all "recursive" copies.
        p.moveToFirstChild()
        while 1:
            fc.putVnode(p,isIgnore)
            if p.hasNext(): p.moveToNext()
            else:           break
        p.moveToParent() # Restore p in the caller.
        fc.put('</v>\n')
    else:
        fc.put('%s</v>\n' % v_head) # Call put only once.
#@+node:ekr.20031218072017.1864:<< Set gnx = tnode index >>
# New in Leo 4.4.8.  Assign v.t.fileIndex here as needed.
if not v.t.fileIndex:
    v.t.fileIndex = g.app.nodeIndices.getNewIndex()

gnx = g.app.nodeIndices.toString(v.t.fileIndex)

if forceWrite or self.usingClipboard:
    v.t.setWriteBit() # 4.2: Indicate we wrote the body text.
#@-node:ekr.20031218072017.1864:<< Set gnx = tnode index >>
#@+node:ekr.20031218072017.1865:<< Append attribute bits to attrs >>
# These string catenations are benign because they rarely happen.
attr = ""
if v.isExpanded(): attr += "E"
if v.isMarked():   attr += "M"
if v.isOrphan():   attr += "O"

# No longer a bottleneck now that we use p.equal rather than p.__cmp__
# Almost 30% of the entire writing time came from here!!!
if not self.use_sax:
    if p.equal(self.topPosition):     attr += "T" # was a bottleneck
    if p.equal(self.currentPosition): attr += "V" # was a bottleneck

if attr:
    attrs.append(' a="%s"' % attr)

# Put the archived *current* position in the *root* positions <v> element.
if self.use_sax and p.equal(self.rootPosition):
    aList = [str(z) for z in self.currentPosition.archivedPosition()]
    d = hasattr(v,'unKnownAttributes') and v.unknownAttributes or {}
    d['str_leo_pos'] = ','.join(aList)
    # g.trace(aList,d)
    v.unknownAttributes = d
#@nonl
#@-node:ekr.20031218072017.1865:<< Append attribute bits to attrs >>
#@+node:ekr.20040324082713:<< Append tnodeList and unKnownAttributes to attrs>>
# Write the tnodeList only for @file nodes.
# New in 4.2: tnode list is in tnode.

# Debugging.
# if v.isAnyAtFileNode():
    # if hasattr(v.t,"tnodeList"):
        # g.trace(v.headString(),len(v.t.tnodeList))
    # else:
        # g.trace(v.headString(),"no tnodeList")

if hasattr(v.t,"tnodeList") and len(v.t.tnodeList) > 0 and v.isAnyAtFileNode():
    if isThin:
        if g.app.unitTesting:
            g.app.unitTestDict["warning"] = True
        g.es("deleting tnode list for",p.headString(),color="blue")
        # This is safe: cloning can't change the type of this node!
        delattr(v.t,"tnodeList")
    else:
        attrs.append(fc.putTnodeList(v)) # New in 4.0

if hasattr(v,"unknownAttributes"): # New in 4.0
    attrs.append(self.putUnknownAttributes(v))

if p.hasChildren() and not forceWrite and not self.usingClipboard:
    # We put the entire tree when using the clipboard, so no need for this.
    attrs.append(self.putDescendentUnknownAttributes(p))
    attrs.append(self.putDescendentAttributes(p))
#@nonl
#@-node:ekr.20040324082713:<< Append tnodeList and unKnownAttributes to attrs>>
#@+node:ekr.20040702085529:<< issue informational messages >>
if isOrphan and isThin:
    g.es("writing erroneous:",p.headString(),color="blue")
    p.clearOrphan()
#@-node:ekr.20040702085529:<< issue informational messages >>
#@-node:ekr.20031218072017.1863:putVnode (3.x and 4.x)
#@+node:ekr.20041005105605.133:Writing (top level)
#@+node:ekr.20041005105605.134:Don't override in plugins
# Plugins probably should not need to override these methods.
#@+node:ekr.20041005105605.135:closeWriteFile
# 4.0: Don't use newline-pending logic.

def closeWriteFile (self):

    at = self

    if at.outputFile:
        at.outputFile.flush()
        if self.toString:
            self.stringOutput = self.outputFile.get()
        at.outputFile.close()
        at.outputFile = None
#@-node:ekr.20041005105605.135:closeWriteFile
#@+node:ekr.20041005105605.136:norefWrite
def norefWrite(self,root,toString=False):

    at = self ; c = at.c
    c.endEditing() # Capture the current headline.

    try:
        targetFileName = root.atNorefFileNodeName()
        at.initWriteIvars(root,targetFileName,nosentinels=False,toString=toString)
        if at.errors: return
        if not at.openFileForWriting(root,targetFileName,toString):
            # openFileForWriting calls root.setDirty() if there are errors.
            return
        << write root's tree >>
        at.closeWriteFile()
        at.replaceTargetFileIfDifferent(root) # Sets/clears dirty and orphan bits.
    except Exception:
        at.writeException(root) # Sets dirty and orphan bits.

rawWrite = norefWrite
#@+node:ekr.20041005105605.137:<< write root's tree >>
<< put all @first lines in root >>
at.putOpenLeoSentinel("@+leo-ver=4")
<< put optional @comment sentinel lines >>

for p in root.self_and_subtree_iter():
    << Write p's node >>

at.putSentinel("@-leo")
<< put all @last lines in root >>
#@+node:ekr.20041005105605.138:<< put all @first lines in root >>
@ Write any @first lines.  These lines are also converted to @verbatim lines, so the read logic simply ignores lines preceding the @+leo sentinel.
@c

s = root.v.t.bodyString
tag = "@first"
i = 0
while g.match(s,i,tag):
    i += len(tag)
    i = g.skip_ws(s,i)
    j = i
    i = g.skip_to_end_of_line(s,i)
    # Write @first line, whether empty or not
    line = s[j:i]
    at.putBuffered(line) ; at.onl()
    i = g.skip_nl(s,i)
#@-node:ekr.20041005105605.138:<< put all @first lines in root >>
#@+node:ekr.20041005105605.139:<< put optional @comment sentinel lines >>
s2 = c.config.output_initial_comment
if s2:
    lines = string.split(s2,"\\n")
    for line in lines:
        line = line.replace("@date",time.asctime())
        if len(line)> 0:
            at.putSentinel("@comment " + line)
#@-node:ekr.20041005105605.139:<< put optional @comment sentinel lines >>
#@+node:ekr.20041005105605.140:<< Write p's node >>
at.putOpenNodeSentinel(p)

s = p.bodyString()

if self.write_strips_blank_lines:
    s = self.cleanLines(p,s)

if s:
    s = g.toEncodedString(s,at.encoding,reportErrors=True)
    at.outputStringWithLineEndings(s)

# Put an @nonl sentinel if s does not end in a newline.
if s and s[-1] != '\n':
    at.onl_sent() ; at.putSentinel("@nonl")

at.putCloseNodeSentinel(p)
#@-node:ekr.20041005105605.140:<< Write p's node >>
#@+node:ekr.20041005105605.141:<< put all @last lines in root >>
@ Write any @last lines.  These lines are also converted to @verbatim lines, so the read logic simply ignores lines following the @-leo sentinel.
@c

tag = "@last"
lines = string.split(root.v.t.bodyString,'\n')
n = len(lines) ; j = k = n - 1
# Don't write an empty last line.
if j >= 0 and len(lines[j])==0:
    j = k = n - 2
# Scan backwards for @last directives.
while j >= 0:
    line = lines[j]
    if g.match(line,0,tag): j -= 1
    else: break
# Write the @last lines.
for line in lines[j+1:k+1]:
    i = len(tag) ; i = g.skip_ws(line,i)
    at.putBuffered(line[i:]) ; at.onl()
#@-node:ekr.20041005105605.141:<< put all @last lines in root >>
#@-node:ekr.20041005105605.137:<< write root's tree >>
#@-node:ekr.20041005105605.136:norefWrite
#@+node:ekr.20041005105605.142:openFileForWriting & openFileForWritingHelper
def openFileForWriting (self,root,fileName,toString):

    at = self
    at.outputFile = None

    if toString:
        at.shortFileName = g.shortFileName(fileName)
        at.outputFileName = "<string: %s>" % at.shortFileName
        at.outputFile = g.fileLikeObject()
    else:
        ok = at.openFileForWritingHelper(fileName)

        # New in Leo 4.4.8: set dirty bit if there are errors.
        if not ok: at.outputFile = None

    # New in 4.3 b2: root may be none when writing from a string.
    if root:
        if at.outputFile:
            root.clearOrphan()
        else:
            root.setOrphan()
            root.setDirty()

    return at.outputFile is not None
#@+node:ekr.20041005105605.143:openFileForWritingHelper
def openFileForWritingHelper (self,fileName):

    '''Open the file and return True if all went well.'''

    at = self ; c = at.c

    try:
        at.shortFileName = g.shortFileName(fileName)
        fileName = g.os_path_join(at.default_directory,fileName)
        at.targetFileName = g.os_path_normpath(fileName)
        path = g.os_path_dirname(at.targetFileName)
        if not path or not g.os_path_exists(path):
            if path:
                path = g.makeAllNonExistentDirectories(path,c=c)
            if not path or not g.os_path_exists(path):
                path = g.os_path_dirname(at.targetFileName)
                at.writeError("path does not exist: " + path)
                return False
    except Exception:
        at.exception("exception creating path: %s" % repr(path))
        g.es_exception()
        return False

    if g.os_path_exists(at.targetFileName):
        try:
            if not os.access(at.targetFileName,os.W_OK):
                at.writeError("can not open: read only: " + at.targetFileName)
                return False
        except AttributeError:
            pass # os.access() may not exist on all platforms.

    try:
        at.outputFileName = at.targetFileName + ".tmp"
        at.outputFile = self.openForWrite(at.outputFileName,'wb') # bwm
        if not at.outputFile:
            at.writeError("can not create " + at.outputFileName)
            return False
    except Exception:
        at.exception("exception creating:" + at.outputFileName)
        return False

    return True
#@-node:ekr.20041005105605.143:openFileForWritingHelper
#@-node:ekr.20041005105605.142:openFileForWriting & openFileForWritingHelper
#@+node:ekr.20041005105605.144:write
# This is the entry point to the write code.  root should be an @file vnode.

def write(self,root,
    nosentinels=False,
    thinFile=False,
    scriptWrite=False,
    toString=False,
    write_strips_blank_lines=None,
):

    """Write a 4.x derived file."""

    at = self ; c = at.c
    c.endEditing() # Capture the current headline.

    << set at.targetFileName >>
    at.initWriteIvars(root,at.targetFileName,
        nosentinels=nosentinels,thinFile=thinFile,
        scriptWrite=scriptWrite,toString=toString,
        write_strips_blank_lines=write_strips_blank_lines)
    if not at.openFileForWriting(root,at.targetFileName,toString):
        # openFileForWriting calls root.setDirty() if there are errors.
        return

    try:
        at.writeOpenFile(root,nosentinels=nosentinels,toString=toString)
        if toString:
            at.closeWriteFile() # sets self.stringOutput
            # Major bug: failure to clear this wipes out headlines!
            # Minor bug: sometimes this causes slight problems...
            at.root.v.t.tnodeList = []
            at.root.v.t._p_changed = True
        else:
            at.closeWriteFile()
            << set dirty and orphan bits on error >>
    except Exception:
        if toString:
            at.exception("exception preprocessing script")
            at.root.v.t.tnodeList = []
            at.root.v.t._p_changed = True
        else:
            at.writeException() # Sets dirty and orphan bits.
#@+node:ekr.20041005105605.145:<< set at.targetFileName >>
if toString:
    at.targetFileName = "<string-file>"
elif nosentinels:
    at.targetFileName = root.atNoSentFileNodeName()
elif thinFile:
    at.targetFileName = root.atThinFileNodeName()
else:
    at.targetFileName = root.atFileNodeName()
#@-node:ekr.20041005105605.145:<< set at.targetFileName >>
#@+node:ekr.20041005105605.146:<< set dirty and orphan bits on error >>
# Setting the orphan and dirty flags tells Leo to write the tree..

if at.errors > 0 or at.root.isOrphan():
    root.setOrphan()
    root.setDirty() # Make _sure_ we try to rewrite this file.
    ### os.remove(at.outputFileName) # Delete the temp file.
    self.remove(at.outputFileName) # Delete the temp file.
    g.es("not written:",at.outputFileName)
else:
    at.replaceTargetFileIfDifferent(root) # Sets/clears dirty and orphan bits.
#@-node:ekr.20041005105605.146:<< set dirty and orphan bits on error >>
#@-node:ekr.20041005105605.144:write
#@+node:ekr.20041005105605.147:writeAll (atFile)
def writeAll(self,
    writeAtFileNodesFlag=False,
    writeDirtyAtFileNodesFlag=False,
    toString=False,
):

    """Write @file nodes in all or part of the outline"""

    at = self ; c = at.c
    writtenFiles = [] # Files that might be written again.
    mustAutoSave = False ; atOk = True

    if writeAtFileNodesFlag:
        # Write all nodes in the selected tree.
        p = c.currentPosition()
        after = p.nodeAfterTree()
    else:
        # Write dirty nodes in the entire outline.
        p =  c.rootPosition()
        after = c.nullPosition()

    << Clear all orphan bits >>
    atOk = True
    while p and p != after:
        if p.isAnyAtFileNode() or p.isAtIgnoreNode():
            << handle v's tree >>
            p.moveToNodeAfterTree()
        else:
            p.moveToThreadNext()

    << say the command is finished >>
    return mustAutoSave,atOk
#@+node:ekr.20041005105605.148:<< Clear all orphan bits >>
@ We must clear these bits because they may have been set on a previous write.
Calls to atFile::write may set the orphan bits in @file nodes.
If so, write_Leo_file will write the entire @file tree.
@c

for v2 in p.self_and_subtree_iter():
    v2.clearOrphan()
#@-node:ekr.20041005105605.148:<< Clear all orphan bits >>
#@+node:ekr.20041005105605.149:<< handle v's tree >>
if p.v.isDirty() or writeAtFileNodesFlag or p.v.t in writtenFiles:

    at.fileChangedFlag = False
    autoSave = False

    # Tricky: @ignore not recognised in @silentfile nodes.
    if p.isAtAsisFileNode():
        at.asisWrite(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True
    elif p.isAtIgnoreNode():
        pass
    elif p.isAtAutoNode():
        at.writeOneAtAutoNode(p,toString=toString,force=False)
        writtenFiles.append(p.v.t)
    elif p.isAtNorefFileNode():
        at.norefWrite(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True
    elif p.isAtNoSentFileNode():
        at.write(p,nosentinels=True,toString=toString)
        writtenFiles.append(p.v.t) # No need for autosave
    elif p.isAtThinFileNode():
        at.write(p,thinFile=True,toString=toString)
        writtenFiles.append(p.v.t) # No need for autosave.
    elif p.isAtFileNode():
        at.write(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True

    if at.errors: atOk = False

    if at.fileChangedFlag and autoSave: # Set by replaceTargetFileIfDifferent.
        mustAutoSave = True
#@-node:ekr.20041005105605.149:<< handle v's tree >>
#@+node:ekr.20041005105605.150:<< say the command is finished >>
if writeAtFileNodesFlag or writeDirtyAtFileNodesFlag:
    if len(writtenFiles) > 0:
        g.es("finished")
    elif writeAtFileNodesFlag:
        g.es("no @file nodes in the selected tree")
    else:
        g.es("no dirty @file nodes")
#@-node:ekr.20041005105605.150:<< say the command is finished >>
#@-node:ekr.20041005105605.147:writeAll (atFile)
#@+node:ekr.20070806105859:writeAtAutoNodes & writeDirtyAtFileNodes (atFile) & helpers
def writeAtAutoNodes (self,event=None):

    '''Write all @auto nodes in the selected outline.'''

    at = self
    at.writeAtAutoNodesHelper(writeDirtyOnly=False)

def writeDirtyAtAutoNodes (self,event=None):

    '''Write all dirty @auto nodes in the selected outline.'''

    at = self
    at.writeAtAutoNodesHelper(writeDirtyOnly=True)
#@nonl
#@+node:ekr.20070806140208:writeAtAutoNodesHelper
def writeAtAutoNodesHelper(self,toString=False,writeDirtyOnly=True):

    """Write @auto nodes in the selected outline"""

    at = self ; c = at.c
    p = c.currentPosition() ; after = p.nodeAfterTree()
    found = False

    while p and p != after:
        if p.isAtAutoNode() and not p.isAtIgnoreNode() and (p.isDirty() or not writeDirtyOnly):
            ok = at.writeOneAtAutoNode(p,toString=toString,force=True)
            if ok:
                found = True
                p.moveToNodeAfterTree()
            else:
                p.moveToThreadNext()
        else:
            p.moveToThreadNext()

    if found:
        g.es("finished")
    elif writeDirtyOnly:
        g.es("no dirty @auto nodes in the selected tree")
    else:
        g.es("no @auto nodes in the selected tree")
#@-node:ekr.20070806140208:writeAtAutoNodesHelper
#@+node:ekr.20070806141607:writeOneAtAutoNode & helpers
def writeOneAtAutoNode(self,p,toString,force):

    '''Write p, an @auto node.

    File indices *must* have already been assigned.'''

    at = self ; c = at.c ; root = p.copy()

    fileName = p.atAutoNodeName()
    if not fileName: return False

    at.scanDefaultDirectory(p,importing=True) # Set default_directory
    fileName = g.os_path_join(at.default_directory,fileName)
    exists = g.os_path_exists(fileName)

    if not toString and not self.shouldWriteAtAutoNode(p,exists,force):
        return False

    # This code is similar to code in at.write.
    c.endEditing() # Capture the current headline.
    at.targetFileName = g.choose(toString,"<string-file>",fileName)
    at.initWriteIvars(root,at.targetFileName,
        nosentinels=True,thinFile=False,scriptWrite=False,
        toString=toString,write_strips_blank_lines=False)

    ok = at.openFileForWriting (root,fileName=fileName,toString=toString)
    if ok:
        at.writeOpenFile(root,nosentinels=True,toString=toString,atAuto=True)
        at.closeWriteFile() # Sets stringOutput if toString is True.
        if at.errors == 0:
            at.replaceTargetFileIfDifferent(root) # Sets/clears dirty and orphan bits.
        else:
            g.es("not written:",at.outputFileName)
            root.setDirty() # New in Leo 4.4.8.

    elif not toString:
        root.setDirty() # Make _sure_ we try to rewrite this file.
        g.es("not written:",at.outputFileName)

    return ok
#@+node:ekr.20071019141745:shouldWriteAtAutoNode
@ Much thought went into this decision tree:

- We do not want decisions to depend on past history.  That's too confusing.
- We must ensure that the file will be written if the user does significant work.
- We must ensure that the user can create an @auto x node at any time
  without risk of of replacing x with empty or insignificant information.
- We want the user to be able to create an @auto node which will be populated the next time the .leo file is opened.
- We don't want minor import imperfections to be written to the @auto file.
- The explicit commands that read and write @auto trees must always be honored.
@c

def shouldWriteAtAutoNode (self,p,exists,force):

    '''Return True if we should write the @auto node at p.'''

    if force: # We are executing write-at-auto-node or write-dirty-at-auto-nodes.
        return True
    elif not exists: # We can write a non-existent file without danger.
        return True
    elif not p.isDirty(): # There is nothing new to write.
        return False
    elif not self.isSignificantAtAutoTree(p): # There is noting of value to write.
        g.es_print(p.headString(),'not written:',color='red')
        g.es_print('no children and less than 10 characters (excluding directives)',color='red')
        return False
    else: # The @auto tree is dirty and contains significant info.
        return True
#@-node:ekr.20071019141745:shouldWriteAtAutoNode
#@+node:ekr.20070909103844:isSignificantAtAutoTree
def isSignificantAtAutoTree (self,p):

    '''Return True if p's tree has a significant amount of information.'''

    s = p.bodyString()

    # Remove all blank lines and all Leo directives.
    lines = []
    for line in g.splitLines(s):
        if not line.strip():
            pass
        elif line.startswith('@'):
            i = 1 ; j = g.skip_id(line,i,chars='-')
            word = s[i:j]
            if not (word and word in g.globalDirectiveList):
                lines.append(line)
        else:
            lines.append(line)

    s2 = ''.join(lines)
    # g.trace('s2',s2)

    return p.hasChildren() or len(s2.strip()) >= 10
#@-node:ekr.20070909103844:isSignificantAtAutoTree
#@-node:ekr.20070806141607:writeOneAtAutoNode & helpers
#@-node:ekr.20070806105859:writeAtAutoNodes & writeDirtyAtFileNodes (atFile) & helpers
#@+node:ekr.20050506084734:writeFromString
# This is at.write specialized for scripting.

def writeFromString(self,root,s,forcePythonSentinels=True,useSentinels=True):

    """Write a 4.x derived file from a string.

    This is used by the scripting logic."""

    at = self ; c = at.c
    c.endEditing() # Capture the current headline, but don't change the focus!

    at.initWriteIvars(root,"<string-file>",
        nosentinels=not useSentinels,thinFile=False,scriptWrite=True,toString=True,
        forcePythonSentinels=forcePythonSentinels)

    try:
        ok = at.openFileForWriting(root,at.targetFileName,toString=True)
        if g.app.unitTesting: assert ok # string writes never fail.
        # Simulate writing the entire file so error recovery works.
        at.writeOpenFile(root,nosentinels=not useSentinels,toString=True,fromString=s)
        at.closeWriteFile()
        # Major bug: failure to clear this wipes out headlines!
        # Minor bug: sometimes this causes slight problems...
        if root:
            root.v.t.tnodeList = []
            root.v.t._p_changed = True
    except Exception:
        at.exception("exception preprocessing script")

    return at.stringOutput
#@-node:ekr.20050506084734:writeFromString
#@+node:ekr.20041005105605.151:writeMissing
def writeMissing(self,p,toString=False):

    at = self ; c = at.c
    writtenFiles = False ; changedFiles = False

    p = p.copy()
    after = p.nodeAfterTree()
    while p and p != after: # Don't use iterator.
        if p.isAtAsisFileNode() or (p.isAnyAtFileNode() and not p.isAtIgnoreNode()):
            at.targetFileName = p.anyAtFileNodeName()
            if at.targetFileName:
                at.targetFileName = g.os_path_join(self.default_directory,at.targetFileName)
                at.targetFileName = g.os_path_normpath(at.targetFileName)
                if not g.os_path_exists(at.targetFileName):
                    ok = at.openFileForWriting(p,at.targetFileName,toString)
                    # openFileForWriting calls p.setDirty() if there are errors.
                    if ok:
                        << write the @file node >>
                        at.closeWriteFile()
            p.moveToNodeAfterTree()
        elif p.isAtIgnoreNode():
            p.moveToNodeAfterTree()
        else:
            p.moveToThreadNext()

    if writtenFiles > 0:
        g.es("finished")
    else:
        g.es("no @file node in the selected tree")

    return changedFiles # So caller knows whether to do an auto-save.
#@+node:ekr.20041005105605.152:<< write the @file node >>
if p.isAtAsisFileNode():
    at.asisWrite(p)
elif p.isAtNorefFileNode():
    at.norefWrite(p)
elif p.isAtNoSentFileNode():
    at.write(p,nosentinels=True)
elif p.isAtFileNode():
    at.write(p)
else: assert(0)

writtenFiles = True

if at.fileChangedFlag: # Set by replaceTargetFileIfDifferent.
    changedFiles = True
#@-node:ekr.20041005105605.152:<< write the @file node >>
#@-node:ekr.20041005105605.151:writeMissing
#@-node:ekr.20041005105605.134:Don't override in plugins
#@+node:ekr.20041005105605.153:Override in plugins...
@

All writing eventually goes through the asisWrite or writeOpenFile methods, so
plugins should need only to override these two methods.

In particular, plugins should not need to override the write, writeAll or
writeMissing methods.
#@+node:ekr.20041005105605.154:asisWrite
def asisWrite(self,root,toString=False):

    at = self ; c = at.c
    c.endEditing() # Capture the current headline.

    try:
        targetFileName = root.atAsisFileNodeName()
        at.initWriteIvars(root,targetFileName,toString=toString)
        if at.errors: return
        if not at.openFileForWriting(root,targetFileName,toString):
            # openFileForWriting calls root.setDirty() if there are errors.
            return
        for p in root.self_and_subtree_iter():
            << Write p's headline if it starts with @@ >>
            << Write p's body >>
        at.closeWriteFile()
        at.replaceTargetFileIfDifferent(root) # Sets/clears dirty and orphan bits.
    except Exception:
        at.writeException(root) # Sets dirty and orphan bits.

silentWrite = asisWrite # Compatibility with old scripts.
#@+node:ekr.20041005105605.155:<< Write p's headline if it starts with @@ >>
s = p.headString()

if g.match(s,0,"@@"):
    s = s[2:]
    if s and len(s) > 0:
        s = g.toEncodedString(s,at.encoding,reportErrors=True) # 3/7/03
        at.outputFile.write(s)
#@-node:ekr.20041005105605.155:<< Write p's headline if it starts with @@ >>
#@+node:ekr.20041005105605.156:<< Write p's body >>
s = p.bodyString()

if self.write_strips_blank_lines:
    s = self.cleanLines(p,s)

if s:
    s = g.toEncodedString(s,at.encoding,reportErrors=True) # 3/7/03
    at.outputStringWithLineEndings(s)
#@-node:ekr.20041005105605.156:<< Write p's body >>
#@-node:ekr.20041005105605.154:asisWrite
#@+node:ekr.20041005105605.157:writeOpenFile
# New in 4.3: must be inited before calling this method.
# New in 4.3 b2: support for writing from a string.

def writeOpenFile(self,root,nosentinels=False,toString=False,fromString='',atAuto=False):

    """Do all writes except asis writes."""

    at = self ; s = g.choose(fromString,fromString,root.v.t.bodyString)

    root.clearAllVisitedInTree() # Clear both vnode and tnode bits.
    root.clearVisitedInTree()

    at.putAtFirstLines(s)
    at.putOpenLeoSentinel("@+leo-ver=4")
    at.putInitialComment()
    at.putOpenNodeSentinel(root)
    at.putBody(root,fromString=fromString)
    at.putCloseNodeSentinel(root)
    at.putSentinel("@-leo")
    root.setVisited()
    at.putAtLastLines(s)

    if atAuto or (not toString and not nosentinels):
        at.warnAboutOrphandAndIgnoredNodes()
#@-node:ekr.20041005105605.157:writeOpenFile
#@-node:ekr.20041005105605.153:Override in plugins...
#@-node:ekr.20041005105605.133:Writing (top level)
#@+node:ekr.20041005105605.212:replaceTargetFileIfDifferent & helper
def replaceTargetFileIfDifferent (self,root):

    '''Create target file as follows:
    1. If target file does not exist, rename output file to target file.
    2. If target file is identical to output file, remove the output file.
    3. If target file is different from output file,
       remove target file, then rename output file to be target file.

    Return True if the original file was changed.
    '''

    assert(self.outputFile is None)

    if self.toString:
        # Do *not* change the actual file or set any dirty flag.
        self.fileChangedFlag = False
        return False

    if root:
        # The default: may be changed later.
        root.clearOrphan()
        root.clearDirty()

    if g.os_path_exists(self.targetFileName):
        if self.compareFiles(self.outputFileName,self.targetFileName,not self.explicitLineEnding):
            # Files are identical.
            ok = self.remove(self.outputFileName)
            if not ok:
                # self.remove gives the error.
                # g.es('error removing temp file',color='red')
                g.es('unchanged:',self.shortFileName)
                if root: root.setDirty() # New in 4.4.8.
            g.es('unchanged:',self.shortFileName)

            self.fileChangedFlag = False
            return False
        else:
            << report if the files differ only in line endings >>
            mode = self.stat(self.targetFileName)
            ok = self.rename(self.outputFileName,self.targetFileName,mode)
            if ok:
                g.es('wrote:    ',self.shortFileName)
            else:
                # self.rename gives the error.
                # g.es('error removing temp file',color='red')
                g.es('unchanged:',self.shortFileName)
                if root: root.setDirty() # New in 4.4.8.

            self.fileChangedFlag = ok
            return ok
    else:
        # Rename the output file.
        ok = self.rename(self.outputFileName,self.targetFileName)
        if ok:
            g.es('created:  ',self.targetFileName)
        else:
            # self.rename gives the error.
            # g.es('error renaming temp file',color='red')
            # g.es('unchanged:',self.targetFileName)
            if root: root.setDirty() # New in 4.4.8.

        # No original file to change. Return value tested by a unit test.
        self.fileChangedFlag = False 
        return False
#@+node:ekr.20041019090322:<< report if the files differ only in line endings >>
if (
    self.explicitLineEnding and
    self.compareFiles(
        self.outputFileName,
        self.targetFileName,
        ignoreLineEndings=True)):

    g.es("correcting line endings in:",self.targetFileName,color="blue")
#@-node:ekr.20041019090322:<< report if the files differ only in line endings >>
#@-node:ekr.20041005105605.212:replaceTargetFileIfDifferent & helper
#@-node:ekr.20080318081653.2:Write dirty bit if write fails
#@+node:ekr.20080326060254.1:Fixed bug involving default_target_language
@nocolor

The new setting is @string target_language, not @language default_target_language.

@color
#@nonl
#@+node:ekr.20041118104831.1:class configSettings (leoCommands)
class configSettings:

    """A class to hold config settings for commanders."""

    @others
#@+node:ekr.20041118104831.2:configSettings.__init__ (c.configSettings)
def __init__ (self,c):

    self.c = c

    # Init these here to keep pylint happy.
    self.default_derived_file_encoding = None
    self.new_leo_file_encoding = None
    self.redirect_execute_script_output_to_log_pane = None
    self.tkEncoding = None

    self.defaultBodyFontSize = g.app.config.defaultBodyFontSize
    self.defaultLogFontSize  = g.app.config.defaultLogFontSize
    self.defaultMenuFontSize = g.app.config.defaultMenuFontSize
    self.defaultTreeFontSize = g.app.config.defaultTreeFontSize

    for key in g.app.config.encodingIvarsDict.keys():
        if key != '_hash':
            self.initEncoding(key)

    for key in g.app.config.ivarsDict.keys():
        if key != '_hash':
            self.initIvar(key)
#@+node:ekr.20041118104240:initIvar
def initIvar(self,key):

    c = self.c

    # N.B. The key is munged.
    bunch = g.app.config.ivarsDict.get(key)
    ivarName = bunch.ivar
    val = g.app.config.get(c,ivarName,kind=None) # kind is ignored anyway.

    if val or not hasattr(self,ivarName):
        # g.trace('c.configSettings',c.shortFileName(),ivarName,val)
        setattr(self,ivarName,val)
#@-node:ekr.20041118104240:initIvar
#@+node:ekr.20041118104414:initEncoding
def initEncoding (self,key):

    c = self.c

    # N.B. The key is munged.
    bunch = g.app.config.encodingIvarsDict.get(key)
    encodingName = bunch.ivar
    encoding = g.app.config.get(c,encodingName,kind='string')

    # New in 4.4b3: use the global setting as a last resort.
    if encoding:
        # g.trace('c.configSettings',c.shortFileName(),encodingName,encoding)
        setattr(self,encodingName,encoding)
    else:
        encoding = getattr(g.app.config,encodingName)
        # g.trace('g.app.config',c.shortFileName(),encodingName,encoding)
        setattr(self,encodingName,encoding)

    if encoding and not g.isValidEncoding(encoding):
        g.es("bad", "%s: %s" % (encodingName,encoding))
#@-node:ekr.20041118104414:initEncoding
#@-node:ekr.20041118104831.2:configSettings.__init__ (c.configSettings)
#@+node:ekr.20041118053731:Getters (c.configSettings)
def get (self,setting,theType):
    '''A helper function: return the commander's setting, checking the type.'''
    return g.app.config.get(self.c,setting,theType)

def getAbbrevDict (self):
    '''return the commander's abbreviation dictionary.'''
    return g.app.config.getAbbrevDict(self.c)

def getBool (self,setting,default=None):
    '''Return the value of @bool setting, or the default if the setting is not found.'''
    return g.app.config.getBool(self.c,setting,default=default)

def getButtons (self):
    '''Return a list of tuples (x,y) for common @button nodes.'''
    return g.app.config.atCommonButtonsList # unusual.

def getColor (self,setting):
    '''Return the value of @color setting.'''
    return g.app.config.getColor(self.c,setting)

def getCommands (self):
    '''Return the list of tuples (headline,script) for common @command nodes.'''
    return g.app.config.atCommonCommandsList # unusual.

def getData (self,setting):
    '''Return a list of non-comment strings in the body text of @data setting.'''
    return g.app.config.getData(self.c,setting)

def getDirectory (self,setting):
    '''Return the value of @directory setting, or None if the directory does not exist.'''
    return g.app.config.getDirectory(self.c,setting)

def getFloat (self,setting):
    '''Return the value of @float setting.'''
    return g.app.config.getFloat(self.c,setting)

def getFontFromParams (self,family,size,slant,weight,defaultSize=12):

    '''Compute a font from font parameters.

    Arguments are the names of settings to be use.
    Default to size=12, slant="roman", weight="normal".

    Return None if there is no family setting so we can use system default fonts.'''

    return g.app.config.getFontFromParams(self.c,
        family, size, slant, weight, defaultSize = defaultSize)

# def getFontDict (self,setting):
    # '''Return the value of @font setting.'''
    # return g.app.config.getFontDict(self.c,setting)

def getInt (self,setting):
    '''Return the value of @int setting.'''
    return g.app.config.getInt(self.c,setting)

def getLanguage (self,setting):
    '''Return the value of @string setting.

    The value of this setting should be a language known to Leo.'''
    return g.app.config.getLanguage(self.c,setting)

def getMenusList (self):
    '''Return the list of entries for the @menus tree.'''
    return g.app.config.menusList # unusual.

def getOpenWith (self):
    '''Return a list of dictionaries corresponding to @openwith nodes.'''
    return g.app.config.getOpenWith(self.c)

def getRatio (self,setting):
    '''Return the value of @float setting.
    Warn if the value is less than 0.0 or greater than 1.0.'''
    return g.app.config.getRatio(self.c,setting)

def getRecentFiles (self):
    '''Return the list of recently opened files.'''
    return g.app.config.getRecentFiles()

def getShortcut (self,shortcutName):
    '''Return the tuple (rawKey,accel) for shortcutName in @shortcuts tree.'''
    return g.app.config.getShortcut(self.c,shortcutName)

def getString (self,setting):
    '''Return the value of @string setting.'''
    return g.app.config.getString(self.c,setting)
#@-node:ekr.20041118053731:Getters (c.configSettings)
#@+node:ekr.20041118195812:Setters... (c.configSettings)
#@+node:ekr.20041118195812.3:setRecentFiles (c.configSettings)
def setRecentFiles (self,files):

    '''Update the recent files list.'''

    # Append the files to the global list.
    g.app.config.appendToRecentFiles(files)
#@-node:ekr.20041118195812.3:setRecentFiles (c.configSettings)
#@+node:ekr.20041118195812.2:set & setString
def set (self,p,setting,val):

    # __pychecker__ = '--no-argsused' # p not used.

    return g.app.config.setString(self.c,setting,val)

setString = set
#@-node:ekr.20041118195812.2:set & setString
#@-node:ekr.20041118195812:Setters... (c.configSettings)
#@-node:ekr.20041118104831.1:class configSettings (leoCommands)
#@+node:ekr.20041119203941:class configClass
class configClass:
    """A class to manage configuration settings."""
    << class data >>
    @others
#@+node:ekr.20041122094813:<<  class data >>
@others

# List of dictionaries to search.  Order not too important.
dictList = [ivarsDict,encodingIvarsDict,defaultsDict]

# Keys are commanders.  Values are optionsDicts.
localOptionsDict = {}

localOptionsList = []

# Keys are setting names, values are type names.
warningsDict = {} # Used by get() or allies.
#@+node:ekr.20041117062717.1:defaultsDict
@ This contains only the "interesting" defaults.
Ints and bools default to 0, floats to 0.0 and strings to "".
@c

defaultBodyFontSize = g.choose(sys.platform=="win32",9,12)
defaultLogFontSize  = g.choose(sys.platform=="win32",8,12)
defaultMenuFontSize = g.choose(sys.platform=="win32",9,12)
defaultTreeFontSize = g.choose(sys.platform=="win32",9,12)

defaultsDict = {'_hash':'defaultsDict'}

defaultsData = (
    # compare options...
    ("ignore_blank_lines","bool",True),
    ("limit_count","int",9),
    ("print_mismatching_lines","bool",True),
    ("print_trailing_lines","bool",True),
    # find/change options...
    ("search_body","bool",True),
    ("whole_word","bool",True),
    # Prefs panel.
    # ("default_target_language","language","python"),
    ("target_language","language","python"), # Bug fix: 6/20,2005.
    ("tab_width","int",-4),
    ("page_width","int",132),
    ("output_doc_chunks","bool",True),
    ("tangle_outputs_header","bool",True),
    # Syntax coloring options...
    # Defaults for colors are handled by leoColor.py.
    ("color_directives_in_plain_text","bool",True),
    ("underline_undefined_section_names","bool",True),
    # Window options...
    ("allow_clone_drags","bool",True),
    ("body_pane_wraps","bool",True),
    ("body_text_font_family","family","Courier"),
    ("body_text_font_size","size",defaultBodyFontSize),
    ("body_text_font_slant","slant","roman"),
    ("body_text_font_weight","weight","normal"),
    ("enable_drag_messages","bool",True),
    ("headline_text_font_family","string",None),
    ("headline_text_font_size","size",defaultLogFontSize),
    ("headline_text_font_slant","slant","roman"),
    ("headline_text_font_weight","weight","normal"),
    ("log_text_font_family","string",None),
    ("log_text_font_size","size",defaultLogFontSize),
    ("log_text_font_slant","slant","roman"),
    ("log_text_font_weight","weight","normal"),
    ("initial_window_height","int",600),
    ("initial_window_width","int",800),
    ("initial_window_left","int",10),
    ("initial_window_top","int",10),
    ("initial_splitter_orientation","string","vertical"),
    ("initial_vertical_ratio","ratio",0.5),
    ("initial_horizontal_ratio","ratio",0.3),
    ("initial_horizontal_secondary_ratio","ratio",0.5),
    ("initial_vertical_secondary_ratio","ratio",0.7),
    ("outline_pane_scrolls_horizontally","bool",False),
    ("split_bar_color","color","LightSteelBlue2"),
    ("split_bar_relief","relief","groove"),
    ("split_bar_width","int",7),
)
#@-node:ekr.20041117062717.1:defaultsDict
#@+node:ekr.20041118062709:define encodingIvarsDict
encodingIvarsDict = {'_hash':'encodingIvarsDict'}

encodingIvarsData = (
    ("default_at_auto_file_encoding","string","utf-8"),
    ("default_derived_file_encoding","string","utf-8"),
    ("new_leo_file_encoding","string","UTF-8"),
        # Upper case for compatibility with previous versions.
    ("tkEncoding","string",None),
        # Defaults to None so it doesn't override better defaults.
)
#@-node:ekr.20041118062709:define encodingIvarsDict
#@+node:ekr.20041117072055:ivarsDict
# Each of these settings sets the corresponding ivar.
# Also, the c.configSettings settings class inits the corresponding commander ivar.
ivarsDict = {'_hash':'ivarsDict'}

ivarsData = (
    ("at_root_bodies_start_in_doc_mode","bool",True),
        # For compatibility with previous versions.
    ("create_nonexistent_directories","bool",False),
    ("output_initial_comment","string",""),
        # "" for compatibility with previous versions.
    ("output_newline","string","nl"),
    ("page_width","int","132"),
    ("read_only","bool",True),
        # Make sure we don't alter an illegal leoConfig.txt file!
    ("redirect_execute_script_output_to_log_pane","bool",False),
    ("relative_path_base_directory","string","!"),
    ("remove_sentinels_extension","string",".txt"),
    ("save_clears_undo_buffer","bool",False),
    ("stylesheet","string",None),
    ("tab_width","int",-4),
    ("target_language","language","python"), # Bug fix: added: 6/20/2005.
    ("trailing_body_newlines","string","asis"),
    ("use_plugins","bool",True),
        # New in 4.3: use_plugins = True by default.
    # use_pysco can not be set by 4.3:  config processing happens too late.
        # ("use_psyco","bool",False),
    ("undo_granularity","string","word"),
        # "char","word","line","node"
    ("write_strips_blank_lines","bool",False),
)
#@-node:ekr.20041117072055:ivarsDict
#@-node:ekr.20041122094813:<<  class data >>
#@+node:ekr.20041117083202:Birth... (g.app.config)
#@+node:ekr.20041117062717.2:ctor (configClass)
def __init__ (self):

    self.atCommonButtonsList = [] # List of info for common @buttons nodes.
    self.atCommonCommandsList = [] # List of info for common @commands nodes.
    self.buttonsFileName = ''
    self.configsExist = False # True when we successfully open a setting file.
    self.defaultFont = None # Set in gui.getDefaultConfigFont.
    self.defaultFontFamily = None # Set in gui.getDefaultConfigFont.
    self.enabledPluginsFileName = None
    self.enabledPluginsString = '' 
    self.globalConfigFile = None # Set in initSettingsFiles
    self.homeFile = None # Set in initSettingsFiles
    self.inited = False
    self.menusList = []
    self.menusFileName = ''
    self.modeCommandsDict = {} # For use by @mode logic. Keys are command names, values are g.Bunches.
    self.myGlobalConfigFile = None
    self.myHomeConfigFile = None
    self.recentFilesFiles = [] # List of g.Bunches describing .leoRecentFiles.txt files.
    self.write_recent_files_as_needed = False # Will be set later.
    self.silent = g.app.silentMode
    # g.trace('c.config.silent',self.silent)

    # Inited later...
    self.panes = None
    self.sc = None
    self.tree = None

    self.initDicts()
    self.initIvarsFromSettings()
    self.initSettingsFiles()
    self.initRecentFiles()
#@-node:ekr.20041117062717.2:ctor (configClass)
#@+node:ekr.20041227063801.2:initDicts
def initDicts (self):

    # Only the settings parser needs to search all dicts.
    self.dictList = [self.defaultsDict]

    for key,kind,val in self.defaultsData:
        self.defaultsDict[self.munge(key)] = g.Bunch(
            setting=key,kind=kind,val=val,tag='defaults')

    for key,kind,val in self.ivarsData:
        self.ivarsDict[self.munge(key)] = g.Bunch(
            ivar=key,kind=kind,val=val,tag='ivars')

    for key,kind,val in self.encodingIvarsData:
        self.encodingIvarsDict[self.munge(key)] = g.Bunch(
            ivar=key,kind=kind,encoding=val,tag='encodings')
#@-node:ekr.20041227063801.2:initDicts
#@+node:ekr.20041117065611.2:initIvarsFromSettings & helpers
def initIvarsFromSettings (self):

    for ivar in self.encodingIvarsDict.keys():
        if ivar != '_hash':
            self.initEncoding(ivar)

    for ivar in self.ivarsDict.keys():
        if ivar != '_hash':
            self.initIvar(ivar)
#@+node:ekr.20041117065611.1:initEncoding
def initEncoding (self,key):

    '''Init g.app.config encoding ivars during initialization.'''

    # N.B. The key is munged.
    bunch = self.encodingIvarsDict.get(key)
    encoding = bunch.encoding
    ivar = bunch.ivar
    # g.trace('g.app.config',ivar,encoding)
    setattr(self,ivar,encoding)

    if encoding and not g.isValidEncoding(encoding):
        g.es("g.app.config: bad encoding:","%s: %s" % (ivar,encoding))
#@-node:ekr.20041117065611.1:initEncoding
#@+node:ekr.20041117065611:initIvar
def initIvar(self,key):

    '''Init g.app.config ivars during initialization.

    This does NOT init the corresponding commander ivars.

    Such initing must be done in setIvarsFromSettings.'''

    # N.B. The key is munged.
    bunch = self.ivarsDict.get(key)
    ivar = bunch.ivar # The actual name of the ivar.
    val = bunch.val

    # g.trace('g.app.config',ivar,key,val)
    setattr(self,ivar,val)
#@-node:ekr.20041117065611:initIvar
#@-node:ekr.20041117065611.2:initIvarsFromSettings & helpers
#@+node:ekr.20041117083202.2:initRecentFiles
def initRecentFiles (self):

    self.recentFiles = []
#@-node:ekr.20041117083202.2:initRecentFiles
#@+node:ekr.20041117083857:initSettingsFiles
def initSettingsFiles (self):

    """Set self.globalConfigFile, self.homeFile, self.machineConfigFile and self.myConfigFile."""

    settingsFile = 'leoSettings.leo'
    mySettingsFile = 'myLeoSettings.leo'
    machineConfigFile = self.getMachineName()

    for ivar,theDir,fileName in (
        ('globalConfigFile',    g.app.globalConfigDir,  settingsFile),
        ('homeFile',            g.app.homeDir,          settingsFile),
        ('myGlobalConfigFile',  g.app.globalConfigDir,  mySettingsFile),
        ('myHomeConfigFile',    g.app.homeDir,          mySettingsFile),
        ('machineConfigFile',   g.app.homeDir,          machineConfigFile),
    ):
        # The same file may be assigned to multiple ivars:
        # readSettingsFiles checks for such duplications.
        path = g.os_path_join(theDir,fileName)
        if g.os_path_exists(path):
            setattr(self,ivar,path)
        else:
            setattr(self,ivar,None)
    if 0:
        g.trace('global file:',self.globalConfigFile)
        g.trace('home file:',self.homeFile)
        g.trace('myGlobal file:',self.myGlobalConfigFile)
        g.trace('myHome file:',self.myHomeConfigFile)
#@nonl
#@+node:ekr.20071211112804:getMachineName
def getMachineName (self):

    try:
        import os
        name = os.getenv('HOSTNAME')
        if not name:
            name = os.getenv('COMPUTERNAME')
        if not name:
            import socket
            name = socket.gethostname()
    except Exception:
        name = ''

    if name:
        name +='LeoSettings.leo'

    # g.trace(name)

    return name
#@-node:ekr.20071211112804:getMachineName
#@-node:ekr.20041117083857:initSettingsFiles
#@-node:ekr.20041117083202:Birth... (g.app.config)
#@+node:ekr.20041117081009:Getters... (g.app.config)
#@+node:ekr.20041123070429:canonicalizeSettingName (munge)
def canonicalizeSettingName (self,name):

    if name is None:
        return None

    name = name.lower()
    for ch in ('-','_',' ','\n'):
        name = name.replace(ch,'')

    return g.choose(name,name,None)

munge = canonicalizeSettingName
#@-node:ekr.20041123070429:canonicalizeSettingName (munge)
#@+node:ekr.20041123092357:config.findSettingsPosition
def findSettingsPosition (self,c,setting):

    """Return the position for the setting in the @settings tree for c."""

    munge = self.munge

    root = self.settingsRoot(c)
    if not root:
        return c.nullPosition()

    setting = munge(setting)

    for p in root.subtree_iter():
        h = munge(p.headString())
        if h == setting:
            return p.copy()

    return c.nullPosition()
#@-node:ekr.20041123092357:config.findSettingsPosition
#@+node:ekr.20041117083141:get & allies (g.app.config)
def get (self,c,setting,kind):

    """Get the setting and make sure its type matches the expected type."""

    if c:
        d = self.localOptionsDict.get(c.hash())
        if d:
            val,junk = self.getValFromDict(d,setting,kind)
            if val is not None:
                # if setting == 'targetlanguage':
                    # g.trace(c.shortFileName(),setting,val,g.callers())
                return val

    for d in self.localOptionsList:
        val,junk = self.getValFromDict(d,setting,kind)
        if val is not None:
            kind = d.get('_hash','<no hash>')
            # if setting == 'targetlanguage':
                # g.trace(kind,setting,val,g.callers())
            return val

    for d in self.dictList:
        val,junk = self.getValFromDict(d,setting,kind)
        if val is not None:
            kind = d.get('_hash','<no hash>')
            # if setting == 'targetlanguage':
                # g.trace(kind,setting,val,g.callers())
            return val

    return None
#@+node:ekr.20041121143823:getValFromDict
def getValFromDict (self,d,setting,requestedType,warn=True):

    '''Look up the setting in d. If warn is True, warn if the requested type
    does not (loosely) match the actual type.
    returns (val,exists)'''

    bunch = d.get(self.munge(setting))
    if not bunch: return None,False

    # g.trace(setting,requestedType,bunch.toString())
    val = bunch.val
    if not self.typesMatch(bunch.kind,requestedType):
        # New in 4.4: make sure the types match.
        # A serious warning: one setting may have destroyed another!
        # Important: this is not a complete test of conflicting settings:
        # The warning is given only if the code tries to access the setting.
        if warn:
            g.es_print('warning: ignoring',bunch.kind,'',setting,'is not',requestedType,color='red')
            g.es_print('there may be conflicting settings!',color='red')
        return None, False
    elif val in (u'None',u'none','None','none','',None):
        return None, True # Exists, but is None
    else:
        # g.trace(setting,val)
        return val, True
#@-node:ekr.20041121143823:getValFromDict
#@+node:ekr.20051015093141:typesMatch
def typesMatch (self,type1,type2):

    '''
    Return True if type1, the actual type, matches type2, the requeseted type.

    The following equivalences are allowed:

    - None matches anything.
    - An actual type of string or strings matches anything.
    - Shortcut matches shortcuts.
    '''

    shortcuts = ('shortcut','shortcuts',)

    return (
        type1 == None or type2 == None or
        type1.startswith('string') or
        type1 == 'int' and type2 == 'size' or
        (type1 in shortcuts and type2 in shortcuts) or
        type1 == type2
    )
#@-node:ekr.20051015093141:typesMatch
#@-node:ekr.20041117083141:get & allies (g.app.config)
#@+node:ekr.20051011105014:exists (g.app.config)
def exists (self,c,setting,kind):

    '''Return true if a setting of the given kind exists, even if it is None.'''

    if c:
        d = self.localOptionsDict.get(c.hash())
        if d:
            junk,found = self.getValFromDict(d,setting,kind)
            if found: return True

    for d in self.localOptionsList:
        junk,found = self.getValFromDict(d,setting,kind)
        if found: return True

    for d in self.dictList:
        junk,found = self.getValFromDict(d,setting,kind)
        if found: return True

    # g.trace('does not exist',setting,kind)
    return False
#@-node:ekr.20051011105014:exists (g.app.config)
#@+node:ekr.20060608224112:getAbbrevDict
def getAbbrevDict (self,c):

    """Search all dictionaries for the setting & check it's type"""

    d = self.get(c,'abbrev','abbrev')
    return d or {}
#@-node:ekr.20060608224112:getAbbrevDict
#@+node:ekr.20041117081009.3:getBool
def getBool (self,c,setting,default=None):

    '''Return the value of @bool setting, or the default if the setting is not found.'''

    val = self.get(c,setting,"bool")

    if val in (True,False):
        return val
    else:
        return default
#@-node:ekr.20041117081009.3:getBool
#@+node:ekr.20070926082018:getButtons
def getButtons (self):

    '''Return a list of tuples (x,y) for common @button nodes.'''

    return g.app.config.atCommonButtonsList
#@-node:ekr.20070926082018:getButtons
#@+node:ekr.20080312071248.7:getCommonCommands
def getCommonAtCommands (self):

    '''Return the list of tuples (headline,script) for common @command nodes.'''

    return g.app.config.atCommonCommandsList
#@-node:ekr.20080312071248.7:getCommonCommands
#@+node:ekr.20041122070339:getColor
def getColor (self,c,setting):

    '''Return the value of @color setting.'''

    return self.get(c,setting,"color")
#@-node:ekr.20041122070339:getColor
#@+node:ekr.20071214140900.1:getData
def getData (self,c,setting):

    '''Return a list of non-comment strings in the body text of @data setting.'''

    return self.get(c,setting,"data")
#@-node:ekr.20071214140900.1:getData
#@+node:ekr.20041117093009.1:getDirectory
def getDirectory (self,c,setting):

    '''Return the value of @directory setting, or None if the directory does not exist.'''

    theDir = self.getString(c,setting)

    if g.os_path_exists(theDir) and g.os_path_isdir(theDir):
         return theDir
    else:
        return None
#@-node:ekr.20041117093009.1:getDirectory
#@+node:ekr.20070224075914.1:getEnabledPlugins
def getEnabledPlugins (self):

    '''Return the body text of the @enabled-plugins node.'''

    return g.app.config.enabledPluginsString
#@-node:ekr.20070224075914.1:getEnabledPlugins
#@+node:ekr.20041117082135:getFloat
def getFloat (self,c,setting):

    '''Return the value of @float setting.'''

    val = self.get(c,setting,"float")
    try:
        val = float(val)
        return val
    except TypeError:
        return None
#@-node:ekr.20041117082135:getFloat
#@+node:ekr.20041117062717.13:getFontFromParams (config)
def getFontFromParams(self,c,family,size,slant,weight,defaultSize=12):

    """Compute a font from font parameters.

    Arguments are the names of settings to be use.
    Default to size=12, slant="roman", weight="normal".

    Return None if there is no family setting so we can use system default fonts."""

    family = self.get(c,family,"family")
    if family in (None,""):
        family = self.defaultFontFamily

    size = self.get(c,size,"size")
    if size in (None,0): size = defaultSize

    slant = self.get(c,slant,"slant")
    if slant in (None,""): slant = "roman"

    weight = self.get(c,weight,"weight")
    if weight in (None,""): weight = "normal"

    # g.trace(g.callers(3),family,size,slant,weight,g.shortFileName(c.mFileName))

    return g.app.gui.getFontFromParams(family,size,slant,weight)
#@-node:ekr.20041117062717.13:getFontFromParams (config)
#@+node:ekr.20041117081513:getInt
def getInt (self,c,setting):

    '''Return the value of @int setting.'''

    val = self.get(c,setting,"int")
    try:
        val = int(val)
        return val
    except TypeError:
        return None
#@-node:ekr.20041117081513:getInt
#@+node:ekr.20041117093009.2:getLanguage
def getLanguage (self,c,setting):

    '''Return the setting whose value should be a language known to Leo.'''

    language = self.getString(c,setting)
    # g.trace(setting,language)

    return language
#@-node:ekr.20041117093009.2:getLanguage
#@+node:ekr.20070926070412:getMenusList
def getMenusList (self):

    '''Return the list of entries for the @menus tree.'''

    return g.app.config.menusList
#@-node:ekr.20070926070412:getMenusList
#@+node:ekr.20070411101643:getOpenWith
def getOpenWith (self,c):

    '''Return a list of dictionaries corresponding to @openwith nodes.'''

    val = self.get(c,'openwithtable','openwithtable')

    return val
#@-node:ekr.20070411101643:getOpenWith
#@+node:ekr.20041122070752:getRatio
def getRatio (self,c,setting):

    '''Return the value of @float setting.

    Warn if the value is less than 0.0 or greater than 1.0.'''

    val = self.get(c,setting,"ratio")
    try:
        val = float(val)
        if 0.0 <= val <= 1.0:
            return val
        else:
            return None
    except TypeError:
        return None
#@-node:ekr.20041122070752:getRatio
#@+node:ekr.20041117062717.11:getRecentFiles
def getRecentFiles (self):

    '''Return the list of recently opened files.'''

    return self.recentFiles
#@-node:ekr.20041117062717.11:getRecentFiles
#@+node:ekr.20041117062717.14:getShortcut (config)
def getShortcut (self,c,shortcutName):

    '''Return rawKey,accel for shortcutName'''

    key = c.frame.menu.canonicalizeMenuName(shortcutName)
    key = key.replace('&','') # Allow '&' in names.

    bunchList = self.get(c,key,"shortcut")
    if bunchList:
        bunchList = [bunch for bunch in bunchList
            if bunch.val and bunch.val.lower() != 'none']
        return key,bunchList
    else:
        return key,[]
#@-node:ekr.20041117062717.14:getShortcut (config)
#@+node:ekr.20041117081009.4:getString
def getString (self,c,setting):

    '''Return the value of @string setting.'''

    return self.get(c,setting,"string")
#@-node:ekr.20041117081009.4:getString
#@+node:ekr.20041120074536:settingsRoot
def settingsRoot (self,c):

    '''Return the position of the @settings tree.'''

    # g.trace(c,c.rootPosition())

    for p in c.allNodes_iter():
        if p.headString().rstrip() == "@settings":
            return p.copy()
    else:
        return c.nullPosition()
#@-node:ekr.20041120074536:settingsRoot
#@-node:ekr.20041117081009:Getters... (g.app.config)
#@+node:ekr.20041118084146:Setters (g.app.config)
#@+node:ekr.20041201080436:appendToRecentFiles (g.app.config)
def appendToRecentFiles (self,files):

    files = [theFile.strip() for theFile in files]

    # g.trace(files)

    def munge(name):
        name = name or ''
        return g.os_path_normpath(name).lower()

    for name in files:
        # Remove all variants of name.
        for name2 in self.recentFiles:
            if munge(name) == munge(name2):
                self.recentFiles.remove(name2)

        self.recentFiles.append(name)
#@-node:ekr.20041201080436:appendToRecentFiles (g.app.config)
#@+node:ekr.20041118084146.1:set (g.app.config)
def set (self,c,setting,kind,val):

    '''Set the setting.  Not called during initialization.'''

    # if kind.startswith('setting'): g.trace(val)

    found = False ;  key = self.munge(setting)
    if c:
        d = self.localOptionsDict.get(c.hash())
        if d: found = True

    if not found:
        theHash = c.hash()
        for d in self.localOptionsList:
            hash2 = d.get('_hash')
            if theHash == hash2:
                found = True ; break

    if not found:
        d = self.dictList [0]

    d[key] = g.Bunch(setting=setting,kind=kind,val=val,tag='setting')

    if 0:
        dkind = d.get('_hash','<no hash: %s>' % c.hash())
        g.trace(dkind,setting,kind,val)
#@-node:ekr.20041118084146.1:set (g.app.config)
#@+node:ekr.20041228042224:setIvarsFromSettings (g.app.config)
def setIvarsFromSettings (self,c):

    '''Init g.app.config ivars or c's ivars from settings.

    - Called from readSettingsFiles with c = None to init g.app.config ivars.
    - Called from c.__init__ to init corresponding commmander ivars.'''

    # Ingore temporary commanders created by readSettingsFiles.
    if not self.inited: return

    # g.trace(c)
    d = self.ivarsDict
    for key in d:
        if key != '_hash':
            bunch = d.get(key)
            if bunch:
                ivar = bunch.ivar # The actual name of the ivar.
                kind = bunch.kind
                val = self.get(c,key,kind) # Don't use bunch.val!
                if c:
                    # g.trace("%20s %s = %s" % (g.shortFileName(c.mFileName),ivar,val))
                    setattr(c,ivar,val)
                else:
                    # g.trace("%20s %s = %s" % ('g.app.config',ivar,val))
                    setattr(self,ivar,val)
#@-node:ekr.20041228042224:setIvarsFromSettings (g.app.config)
#@+node:ekr.20041118084241:setString
def setString (self,c,setting,val):

    self.set(c,setting,"string",val)
#@-node:ekr.20041118084241:setString
#@-node:ekr.20041118084146:Setters (g.app.config)
#@+node:ekr.20041117093246:Scanning @settings (g.app.config)
#@+node:ekr.20041120064303:g.app.config.readSettingsFiles & helpers
def readSettingsFiles (self,fileName,verbose=True):

    seen = []
    self.write_recent_files_as_needed = False # Will be set later.
    << define localDirectory, localConfigFile & myLocalConfigFile >>

    # Init settings from leoSettings.leo and myLeoSettings.leo files.
    for path,localFlag in (
        (self.globalConfigFile,False),
        (self.homeFile,False),
        (localConfigFile,False),
        (self.myGlobalConfigFile,False),
        (self.myHomeConfigFile,False),
        (self.machineConfigFile,False),
        (myLocalConfigFile,False),
        (fileName,True),
    ):
        if path and path.lower() not in seen:
            seen.append(path.lower())
            if verbose and not g.app.unitTesting and not self.silent and not g.app.batchMode:
                s = 'reading settings in %s' % path
                # This occurs early in startup, so use the following instead of g.es_print.
                s = g.toEncodedString(s,'ascii')
                print s
                g.app.logWaiting.append((s+'\n','blue'),)

            c = self.openSettingsFile(path)
            if c:
                self.updateSettings(c,localFlag)
                g.app.destroyWindow(c.frame)
                self.write_recent_files_as_needed = c.config.getBool('write_recent_files_as_needed')
                self.setIvarsFromSettings(c)
    self.readRecentFiles(localConfigFile)
    self.inited = True
    self.setIvarsFromSettings(None)
#@+node:ekr.20061028082834:<< define localDirectory, localConfigFile & myLocalConfigFile >>
# This can't be done in initSettingsFiles because the local directory does not exits.
localDirectory = g.os_path_dirname(fileName)

#  Set the local leoSettings.leo file.
localConfigFile = g.os_path_join(localDirectory,'leoSettings.leo')
if not g.os_path_exists(localConfigFile):
    localConfigFile = None

# Set the local myLeoSetting.leo file.
myLocalConfigFile = g.os_path_join(localDirectory,'myLeoSettings.leo')
if not g.os_path_exists(myLocalConfigFile):
    myLocalConfigFile = None
#@nonl
#@-node:ekr.20061028082834:<< define localDirectory, localConfigFile & myLocalConfigFile >>
#@+node:ekr.20041117085625:g.app.config.openSettingsFile
def openSettingsFile (self,path):

    theFile,isZipped = g.openLeoOrZipFile(path)
    if not theFile: return None

    # Similar to g.openWithFileName except it uses a null gui.
    # Changing g.app.gui here is a major hack.
    oldGui = g.app.gui
    g.app.gui = leoGui.nullGui("nullGui")
    c,frame = g.app.newLeoCommanderAndFrame(
        fileName=path,relativeFileName=None,
        initEditCommanders=False,updateRecentFiles=False)
    frame.log.enable(False)
    c.setLog()
    g.app.lockLog()
    ok = frame.c.fileCommands.open(
        theFile,path,readAtFileNodesFlag=False,silent=True) # closes theFile.
    g.app.unlockLog()
    frame.openDirectory = g.os_path_dirname(path)
    g.app.gui = oldGui
    return ok and c
#@-node:ekr.20041117085625:g.app.config.openSettingsFile
#@+node:ekr.20051013161232:g.app.config.updateSettings
def updateSettings (self,c,localFlag):

    d = self.readSettings(c)

    if d:
        d['_hash'] = theHash = c.hash()
        if localFlag:
            self.localOptionsDict[theHash] = d
        else:
            self.localOptionsList.insert(0,d)

    if 0: # Good trace.
        if localFlag:
            g.trace(c.fileName())
            g.trace(d and d.keys())
#@-node:ekr.20051013161232:g.app.config.updateSettings
#@-node:ekr.20041120064303:g.app.config.readSettingsFiles & helpers
#@+node:ekr.20041117083857.1:g.app.config.readSettings
# Called to read all leoSettings.leo files.
# Also called when opening an .leo file to read @settings tree.

def readSettings (self,c):

    """Read settings from a file that may contain an @settings tree."""

    # g.trace(c.fileName())

    # Create a settings dict for c for set()
    if c and self.localOptionsDict.get(c.hash()) is None:
        self.localOptionsDict[c.hash()] = {}

    parser = settingsTreeParser(c)
    d = parser.traverse()

    return d
#@-node:ekr.20041117083857.1:g.app.config.readSettings
#@-node:ekr.20041117093246:Scanning @settings (g.app.config)
#@+node:ekr.20050424114937.1:Reading and writing .leoRecentFiles.txt (g.app.config)
#@+node:ekr.20070224115832:readRecentFiles & helpers
def readRecentFiles (self,localConfigFile):

    '''Read all .leoRecentFiles.txt files.'''

    # The order of files in this list affects the order of the recent files list.
    seen = [] 
    localConfigPath = g.os_path_dirname(localConfigFile)
    for path in (
        g.app.homeDir,
        g.app.globalConfigDir,
        localConfigPath,
    ):
        if path and path not in seen:
            ok = self.readRecentFilesFile(path)
            if ok: seen.append(path)
    if not seen and self.write_recent_files_as_needed:
        self.createRecentFiles()
#@nonl
#@+node:ekr.20061010121944:createRecentFiles
def createRecentFiles (self):

    '''Trye to reate .leoRecentFiles.txt in
    - the users home directory first,
    - Leo's config directory second.'''

    for theDir in (g.app.homeDir,g.app.globalConfigDir):
        if theDir:
            try:
                fileName = g.os_path_join(theDir,'.leoRecentFiles.txt')
                f = file(fileName,'w')
                f.close()
                g.es_print('created',fileName,color='red')
                return
            except Exception:
                g.es_print('can not create',fileName,color='red')
                g.es_exception()
#@nonl
#@-node:ekr.20061010121944:createRecentFiles
#@+node:ekr.20050424115658:readRecentFilesFile
def readRecentFilesFile (self,path):

    fileName = g.os_path_join(path,'.leoRecentFiles.txt')
    ok = g.os_path_exists(fileName)
    if ok:
        if not g.unitTesting and not self.silent:
            print ('reading %s' % fileName)
        lines = file(fileName).readlines()
        if lines and self.munge(lines[0])=='readonly':
            lines = lines[1:]
        if lines:
            lines = [g.toUnicode(g.os_path_normpath(line),'utf-8') for line in lines]
            self.appendToRecentFiles(lines)

    return ok
#@nonl
#@-node:ekr.20050424115658:readRecentFilesFile
#@-node:ekr.20070224115832:readRecentFiles & helpers
#@+node:ekr.20050424114937.2:writeRecentFilesFile & helper
recentFileMessageWritten = False

def writeRecentFilesFile (self,c):

    '''Write the appropriate .leoRecentFiles.txt file.'''

    tag = '.leoRecentFiles.txt'

    if g.app.unitTesting:
        return

    localFileName = c.fileName()
    if localFileName:
        localPath,junk = g.os_path_split(localFileName)
    else:
        localPath = None

    written = False
    for path in (localPath,g.app.globalConfigDir,g.app.homeDir):
        if path:
            fileName = g.os_path_join(path,tag)
            if g.os_path_exists(fileName):
                if not self.recentFileMessageWritten:
                    print ('wrote recent file: %s' % fileName)
                    written = True
                self.writeRecentFilesFileHelper(fileName)
                # Bug fix: Leo 4.4.6: write *all* recent files.

    if written:
        self.recentFileMessageWritten = True
    else:
        pass # g.trace('----- not found: %s' % g.os_path_join(localPath,tag))
#@+node:ekr.20050424131051:writeRecentFilesFileHelper
def writeRecentFilesFileHelper (self,fileName):
    # g.trace(fileName)

    # Don't update the file if it begins with read-only.
    theFile = None
    try:
        theFile = file(fileName)
        lines = theFile.readlines()
        if lines and self.munge(lines[0])=='readonly':
            # g.trace('read-only: %s' %fileName)
            return
    except IOError:
        # The user may have erased a file.  Not an error.
        if theFile: theFile.close()

    theFile = None
    try:
        # g.trace('writing',fileName)
        theFile = file(fileName,'w')
        if self.recentFiles:
            lines = [g.toEncodedString(line,'utf-8') for line in self.recentFiles]
            theFile.write('\n'.join(lines))
            # g.trace(fileName,'lines\n%s' % lines)
        else:
            theFile.write('\n')

    except IOError:
        # The user may have erased a file.  Not an error.
        pass

    except Exception:
        g.es('unexpected exception writing',fileName,color='red')
        g.es_exception()

    if theFile:
        theFile.close()
#@-node:ekr.20050424131051:writeRecentFilesFileHelper
#@-node:ekr.20050424114937.2:writeRecentFilesFile & helper
#@-node:ekr.20050424114937.1:Reading and writing .leoRecentFiles.txt (g.app.config)
#@+node:ekr.20070418073400:g.app.config.printSettings & helper
def printSettings (self,c):

    '''Prints the value of every setting, except key bindings and commands and open-with tables.
    The following letters indicate where the active setting came from:

    - D indicates default settings.
    - F indicates the file being loaded,
    - L indicates leoSettings.leo,
    - M indicates myLeoSettings.leo,
    '''

    settings = {} # Keys are setting names, values are (letter,val)

    if c:
        d = self.localOptionsDict.get(c.hash())
        self.printSettingsHelper(settings,d,letter='[F]')

    for d in self.localOptionsList:
        self.printSettingsHelper(settings,d)

    for d in self.dictList:
        self.printSettingsHelper(settings,d)

    keys = settings.keys() ; keys.sort()
    for key in keys:
        data = settings.get(key)
        letter,val = data
        print '%45s = %s %s' % (key,letter,val)
        g.es('','%s %s = %s' % (letter,key,val))
#@nonl
#@+node:ekr.20070418075804:printSettingsHelper
def printSettingsHelper(self,settings,d,letter=None):

    suppressKind = ('shortcut','shortcuts','openwithtable')
    suppressKeys = (None,'_hash','shortcut')

    if d:
        << set letter >>
        for key in d.keys():
            if key not in suppressKeys and key not in settings.keys():
                bunch = d.get(key)
                if bunch.kind not in suppressKind:
                    settings[key] = (letter,bunch.val)
#@nonl
#@+node:ekr.20070418084502:<< set letter >>
theHash = d.get('_hash').lower()

if letter:
    pass
elif theHash.endswith('myleosettings.leo'):
    letter = '[M]'
elif theHash.endswith('leosettings.leo'):
    letter = ' ' * 3
else:
    letter = '[D]'

# g.trace(letter,theHash)
#@nonl
#@-node:ekr.20070418084502:<< set letter >>
#@-node:ekr.20070418075804:printSettingsHelper
#@-node:ekr.20070418073400:g.app.config.printSettings & helper
#@-node:ekr.20041119203941:class configClass
#@+node:ekr.20041117083141:get & allies (g.app.config)
def get (self,c,setting,kind):

    """Get the setting and make sure its type matches the expected type."""

    if c:
        d = self.localOptionsDict.get(c.hash())
        if d:
            val,junk = self.getValFromDict(d,setting,kind)
            if val is not None:
                # if setting == 'targetlanguage':
                    # g.trace(c.shortFileName(),setting,val,g.callers())
                return val

    for d in self.localOptionsList:
        val,junk = self.getValFromDict(d,setting,kind)
        if val is not None:
            kind = d.get('_hash','<no hash>')
            # if setting == 'targetlanguage':
                # g.trace(kind,setting,val,g.callers())
            return val

    for d in self.dictList:
        val,junk = self.getValFromDict(d,setting,kind)
        if val is not None:
            kind = d.get('_hash','<no hash>')
            # if setting == 'targetlanguage':
                # g.trace(kind,setting,val,g.callers())
            return val

    return None
#@+node:ekr.20041121143823:getValFromDict
def getValFromDict (self,d,setting,requestedType,warn=True):

    '''Look up the setting in d. If warn is True, warn if the requested type
    does not (loosely) match the actual type.
    returns (val,exists)'''

    bunch = d.get(self.munge(setting))
    if not bunch: return None,False

    # g.trace(setting,requestedType,bunch.toString())
    val = bunch.val
    if not self.typesMatch(bunch.kind,requestedType):
        # New in 4.4: make sure the types match.
        # A serious warning: one setting may have destroyed another!
        # Important: this is not a complete test of conflicting settings:
        # The warning is given only if the code tries to access the setting.
        if warn:
            g.es_print('warning: ignoring',bunch.kind,'',setting,'is not',requestedType,color='red')
            g.es_print('there may be conflicting settings!',color='red')
        return None, False
    elif val in (u'None',u'none','None','none','',None):
        return None, True # Exists, but is None
    else:
        # g.trace(setting,val)
        return val, True
#@-node:ekr.20041121143823:getValFromDict
#@+node:ekr.20051015093141:typesMatch
def typesMatch (self,type1,type2):

    '''
    Return True if type1, the actual type, matches type2, the requeseted type.

    The following equivalences are allowed:

    - None matches anything.
    - An actual type of string or strings matches anything.
    - Shortcut matches shortcuts.
    '''

    shortcuts = ('shortcut','shortcuts',)

    return (
        type1 == None or type2 == None or
        type1.startswith('string') or
        type1 == 'int' and type2 == 'size' or
        (type1 in shortcuts and type2 in shortcuts) or
        type1 == type2
    )
#@-node:ekr.20051015093141:typesMatch
#@-node:ekr.20041117083141:get & allies (g.app.config)
#@+node:ekr.20031218072017.2062:getPrefs
# Note: Leo 4.3 does not write these settings to local .leo files.
# Instead, corresponding settings are contained in leoConfig.leo files.

def getPrefs (self):

    c = self.c

    if self.getOpenTag("<preferences"):
        return # <preferences/> seen

    table = (
        ("allow_rich_text",None,None), # Ignored.
        ("tab_width","tab_width",self.getLong),
        ("page_width","page_width",self.getLong),
        ("tangle_bat","tangle_batch_flag",self.getBool),
        ("untangle_bat","untangle_batch_flag",self.getBool),
        ("output_doc_chunks","output_doc_flag",self.getBool),
        ("noweb_flag",None,None), # Ignored.
        ("extended_noweb_flag",None,None), # Ignored.
        ("defaultTargetLanguage","target_language",self.getTargetLanguage),
        ("use_header_flag","use_header_flag",self.getBool))

    done = False
    while 1:
        found = False
        for tag,var,f in table:
            if self.matchTag("%s=" % tag):
                if var:
                    self.getDquote() ; val = f() ; self.getDquote()
                    setattr(c,var,val)
                    # g.trace(var,val)
                else:
                    self.getDqString()
                found = True ; break
        if not found:
            if self.matchTag("/>"):
                done = True ; break
            if self.matchTag(">"):
                break
            else: # New in 4.1: ignore all other tags.
                self.getUnknownTag()

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

    # Must match longer tags before short prefixes.
    for name in g.app.language_delims_dict.keys():
        if self.matchTagWordIgnoringCase(name):
            language = name.replace("/","")
            # self.getDquote()
            return language

    return "c" # default
#@-node:ekr.20031218072017.2063:getTargetLanguage
#@-node:ekr.20031218072017.2062:getPrefs
#@+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.'''

    w = self.editWidget(event)
    if not w: return 'break'
    << set local vars >>
    #g.trace('ch',repr(ch))
    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
    # 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.20061103114242:<< set local vars >>
c = self.c
p = c.currentPosition()
gui = g.app.gui
ch = gui.eventChar(event)
keysym = gui.eventKeysym(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.bodyString() or ''
undoType = 'Typing'
trace = c.config.getBool('trace_masterCommand')
brackets = self.openBracketsList + self.closeBracketsList
inBrackets = ch and g.toUnicode(ch,g.app.tkEncoding) in brackets
if trace: g.trace(name,repr(ch),ch and ch in brackets)
#@nonl
#@-node:ekr.20061103114242:<< set local vars >>
#@+node:ekr.20051026171121:insertNewlineHelper
def insertNewlineHelper (self,w,oldSel,undoType):

    c = self.c ; p = c.currentPosition()
    i,j = oldSel ; ch = '\n'

    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)

        allow_in_nocolor = c.config.getBool('autoindent_in_nocolor_mode')
        if (
            (allow_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()
#@nonl
#@-node:ekr.20051026171121:insertNewlineHelper
#@+node:ekr.20060804095512:initBracketMatcher
def initBracketMatcher (self,c):

    self.openBracketsList  = c.config.getString('open_flash_brackets')  or '([{'
    self.closeBracketsList = c.config.getString('close_flash_brackets') or ')]}'

    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.20060627083506:flashMatchingBracketsHelper
def flashMatchingBracketsHelper (self,w,i,ch):

    d = {}
    if ch in self.openBracketsList:
        for z in xrange(len(self.openBracketsList)):
            d [self.openBracketsList[z]] = self.closeBracketsList[z]
        reverse = False # Search forward
    else:
        for z in xrange(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.20060627091557:flashCharacter
def flashCharacter(self,w,i):

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

    w.flashCharacter(i,bg,fg,flashes,delay)
#@-node:ekr.20060627091557:flashCharacter
#@+node:ekr.20051027172949:updateAutomatchBracket
def updateAutomatchBracket (self,p,w,ch,oldSel):

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

    c = self.c ; d = g.scanDirectives(c,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.20051026171121.1:udpateAutoIndent
def updateAutoIndent (self,p,w):

    c = self.c ; d = g.scanDirectives(c,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.scanColorDirectives(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:udpateAutoIndent
#@+node:ekr.20051026092433:updateTab
def updateTab (self,p,w):

    c = self.c
    d = g.scanDirectives(c,p)
    tab_width = d.get("tabwidth",c.tab_width)
    i,j = w.getSelectionRange()
        # Returns insert point if no selection, with i <= j.

    if i != j:
        w.delete(i,j)

    if tab_width > 0:
        w.insert(i,'\t')
        ins = i+1
    else:
        # Get the preceeding characters.
        s = w.getAllText()
        start = g.skip_to_start_of_line(s,i)
        s2 = s[start:i]

        # Compute n, the number of spaces to insert.
        width = g.computeWidth(s2,tab_width)
        n = abs(tab_width) - (width % abs(tab_width))
        # g.trace('n',n)
        w.insert(i,' ' * n)
        ins = i+n

    w.setSelectionRange(ins,ins,insert=ins)
#@nonl
#@-node:ekr.20051026092433:updateTab
#@-node:ekr.20051125080855:selfInsertCommand, helpers
#@-node:ekr.20080326060254.1:Fixed bug involving default_target_language
#@+node:ekr.20080327061021.3:Fixed plugins that create new menu items
@nocolor

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

Just noticed some plugins (rst3, graphed) which place entries in some
of the standard menus aren't placing those entries there now.

Alt-X write-restructured-text works, so I think the plugin's installed
no problem (it's in the plugins menu too), but its normal "Write
restructured text" entry in the Edit menu is missing.

What I did:

- Changed menu1 to menu2 in several plugins.
- Changed menu2 to menu-update.
- Moved after-create-leo-frame to after menu2.


@color
#@-node:ekr.20080327061021.3:Fixed plugins that create new menu items
#@-node:ekr.20080315115427.575:Fixed bugs
#@+node:ekr.20080315115427.576:Features
#@+node:ekr.20080315083057.8:Added @bool collapse_nodes_while_spelling setting
# This hugely speeds up spelling when there are lots of misspelled words.
#@nonl
#@+node:ekr.20051025071455.45:findNextMisspelledWord
def findNextMisspelledWord(self):
    """Find the next unknown word."""

    c = self.c ; p = c.currentPosition()
    w = c.frame.body.bodyCtrl
    aspell = self.aspell ; alts = None ; word = None
    sparseFind = c.config.getBool('collapse_nodes_while_spelling')
    trace = False
    try:
        while 1:
            i,j,p,word = self.findNextWord(p)
            # g.trace(i,j,p and p.headString() or '<no p>')
            if not p or not word:
                alts = None
                break
            << Skip word if ignored or in local dictionary >>
            alts = aspell.processWord(word)
            if trace: g.trace('alts',alts and len(alts) or 0,i,j,word,p and p.headString() or 'None')
            if alts:
                c.beginUpdate()
                try:
                    redraw = not p.isVisible(c)
                    # New in Leo 4.4.8: show only the 'sparse' tree when redrawing.
                    if sparseFind and not c.currentPosition().isAncestorOf(p):
                        for p2 in c.currentPosition().self_and_parents_iter():
                            p2.contract()
                            redraw = True
                    for p2 in p.parents_iter():
                        if not p2.isExpanded():
                            p2.expand()
                            redraw = True
                    # c.frame.tree.expandAllAncestors(p)
                    c.selectPosition(p)
                finally:
                    c.endUpdate(redraw)
                    w.setSelectionRange(i,j,insert=j)
                break
    except Exception:
        g.es_exception()
    return alts, word
#@+node:ekr.20051025071455.46:<< Skip word if ignored or in local dictionary >>
@ 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
@c

if self.dictionary.has_key(word.lower()):
    continue
#@-node:ekr.20051025071455.46:<< Skip word if ignored or in local dictionary >>
#@-node:ekr.20051025071455.45:findNextMisspelledWord
#@-node:ekr.20080315083057.8:Added @bool collapse_nodes_while_spelling setting
#@+node:ekr.20080315115427.1:give line numbers when errors reading derived files
#@+node:ekr.20041005105605.74:scanText4 & allies
def scanText4 (self,theFile,fileName,p,verbose=False):

    """Scan a 4.x derived file non-recursively."""

    # __pychecker__ = '--no-argsused' # fileName,verbose might be used for debugging.

    at = self
    << init ivars for scanText4 >>
    while at.errors == 0 and not at.done:
        s = at.readLine(theFile)
        self.lineNumber += 1
        if len(s) == 0: break
        kind = at.sentinelKind4(s)
        # g.trace(at.sentinelName(kind),s.strip())
        if kind == at.noSentinel:
            i = 0
        else:
            i = at.skipSentinelStart4(s,0)
        func = at.dispatch_dict[kind]
        func(s,i)

    if at.errors == 0 and not at.done:
        << report unexpected end of text >>

    return at.lastLines
#@+node:ekr.20041005105605.75:<< init ivars for scanText4 >>
# Unstacked ivars...
at.cloneSibCount = 0
at.done = False
at.inCode = True
at.indent = 0 # Changed only for sentinels.
at.lastLines = [] # The lines after @-leo
at.leadingWs = ""
at.lineNumber = 0
at.root = p.copy() # Bug fix: 12/10/05
at.rootSeen = False
at.updateWarningGiven = False

# Stacked ivars...
at.endSentinelStack = [at.endLeo] # We have already handled the @+leo sentinel.
at.out = [] ; at.outStack = []
at.t = p.v.t ; at.tStack = []
at.lastThinNode = p.v ; at.thinNodeStack = [p.v]

if 0: # Useful for debugging.
    if hasattr(p.v.t,"tnodeList"):
        g.trace("len(tnodeList)",len(p.v.t.tnodeList),p.v)
    else:
        g.trace("no tnodeList",p.v)

# g.trace(at.startSentinelComment)
#@-node:ekr.20041005105605.75:<< init ivars for scanText4 >>
#@+node:ekr.20041005105605.76:<< report unexpected end of text >>
assert(at.endSentinelStack)

at.readError(
    "Unexpected end of file. Expecting %s sentinel" %
    at.sentinelName(at.endSentinelStack[-1]))
#@-node:ekr.20041005105605.76:<< report unexpected end of text >>
#@+node:ekr.20041005105605.77:readNormalLine
def readNormalLine (self,s,i):

    at = self

    if at.inCode:
        if not at.raw:
            s = g.removeLeadingWhitespace(s,at.indent,at.tab_width)
        at.out.append(s)
    else:
        << Skip the leading stuff >>
        << Append s to docOut >>
#@+node:ekr.20041005105605.78:<< Skip the leading stuff >>
if len(at.endSentinelComment) == 0:
    # Skip the single comment delim and a blank.
    i = g.skip_ws(s,0)
    if g.match(s,i,at.startSentinelComment):
        i += len(at.startSentinelComment)
        if g.match(s,i," "): i += 1
else:
    i = at.skipIndent(s,0,at.indent)
#@-node:ekr.20041005105605.78:<< Skip the leading stuff >>
#@+node:ekr.20041005105605.79:<< Append s to docOut >>
line = s[i:-1] # remove newline for rstrip.

if line == line.rstrip():
    # no trailing whitespace: the newline is real.
    at.docOut.append(line + '\n')
else:
    # trailing whitespace: the newline is fake.
    at.docOut.append(line)
#@-node:ekr.20041005105605.79:<< Append s to docOut >>
#@-node:ekr.20041005105605.77:readNormalLine
#@+node:ekr.20041005105605.80:start sentinels
#@+node:ekr.20041005105605.81:readStartAll (4.2)
def readStartAll (self,s,i):

    """Read an @+all sentinel."""

    at = self
    j = g.skip_ws(s,i)
    leadingWs = s[i:j]
    if leadingWs:
        assert(g.match(s,j,"@+all"))
    else:
        assert(g.match(s,j,"+all"))

    # Make sure that the generated at-all is properly indented.
    at.out.append(leadingWs + "@all\n")

    at.endSentinelStack.append(at.endAll)
#@-node:ekr.20041005105605.81:readStartAll (4.2)
#@+node:ekr.20041005105605.82:readStartAt & readStartDoc
def readStartAt (self,s,i):
    """Read an @+at sentinel."""
    at = self ; assert(g.match(s,i,"+at"))
    if 0:# new code: append whatever follows the sentinel.
        i += 3 ; j = at.skipToEndSentinel(s,i) ; follow = s[i:j]
        at.out.append('@' + follow) ; at.docOut = []
    else:
        i += 3 ; j = g.skip_ws(s,i) ; ws = s[i:j]
        at.docOut = ['@' + ws + '\n'] # This newline may be removed by a following @nonl
    at.inCode = False
    at.endSentinelStack.append(at.endAt)

def readStartDoc (self,s,i):
    """Read an @+doc sentinel."""
    at = self ; assert(g.match(s,i,"+doc"))
    if 0: # new code: append whatever follows the sentinel.
        i += 4 ; j = at.skipToEndSentinel(s,i) ; follow = s[i:j]
        at.out.append('@' + follow) ; at.docOut = []
    else:
        i += 4 ; j = g.skip_ws(s,i) ; ws = s[i:j]
        at.docOut = ["@doc" + ws + '\n'] # This newline may be removed by a following @nonl
    at.inCode = False
    at.endSentinelStack.append(at.endDoc)

def skipToEndSentinel(self,s,i):
    at = self
    end = at.endSentinelComment
    if end:
        j = s.find(end,i)
        if j == -1:
            return g.skip_to_end_of_line(s,i)
        else:
            return j
    else:
        return g.skip_to_end_of_line(s,i)
#@-node:ekr.20041005105605.82:readStartAt & readStartDoc
#@+node:ekr.20041005105605.83:readStartLeo
def readStartLeo (self,s,i):

    """Read an unexpected @+leo sentinel."""

    at = self
    assert(g.match(s,i,"+leo"))
    at.readError("Ignoring unexpected @+leo sentinel")
#@-node:ekr.20041005105605.83:readStartLeo
#@+node:ekr.20041005105605.84:readStartMiddle
def readStartMiddle (self,s,i):

    """Read an @+middle sentinel."""

    at = self

    at.readStartNode(s,i,middle=True)
#@-node:ekr.20041005105605.84:readStartMiddle
#@+node:ekr.20041005105605.85:readStartNode (4.x)
def readStartNode (self,s,i,middle=False):

    """Read an @+node or @+middle sentinel."""

    at = self
    if middle:
        assert(g.match(s,i,"+middle:"))
        i += 8
    else:
        assert(g.match(s,i,"+node:"))
        i += 6

    if at.thinFile:
        << set gnx and bump i >>
    << Set headline, undoing the CWEB hack >>
    if not at.root_seen:
        at.root_seen = True
        << Check the filename in the sentinel >>

    i,newIndent = g.skip_leading_ws_with_indent(s,0,at.tab_width)
    at.indentStack.append(at.indent) ; at.indent = newIndent

    at.outStack.append(at.out) ; at.out = []
    at.tStack.append(at.t)

    if at.importing:
        p = at.createImportedNode(at.root,headline)
        at.t = p.v.t
    elif at.thinFile:
        at.thinNodeStack.append(at.lastThinNode)
        at.lastThinNode = v = at.createThinChild4(gnx,headline)
        at.t = v.t
    else:
        at.t = at.findChild4(headline)

    at.endSentinelStack.append(at.endNode)
#@+node:ekr.20041005105605.86:<< set gnx and bump i >>
# We have skipped past the opening colon of the gnx.
j = s.find(':',i)
if j == -1:
    g.trace("no closing colon",g.get_line(s,i))
    at.readError("Expecting gnx in @+node sentinel")
    return # 5/17/04
else:
    gnx = s[i:j]
    i = j + 1 # Skip the i
#@-node:ekr.20041005105605.86:<< set gnx and bump i >>
#@+node:ekr.20041005105605.87:<< Set headline, undoing the CWEB hack >>
# Set headline to the rest of the line.
# Don't strip leading whitespace."

if len(at.endSentinelComment) == 0:
    headline = s[i:-1].rstrip()
else:
    k = s.rfind(at.endSentinelComment,i)
    headline = s[i:k].rstrip() # works if k == -1

# Undo the CWEB hack: undouble @ signs if the opening comment delim ends in '@'.
if at.startSentinelComment[-1:] == '@':
    headline = headline.replace('@@','@')
#@-node:ekr.20041005105605.87:<< Set headline, undoing the CWEB hack >>
#@+node:ekr.20041005105605.88:<< Check the filename in the sentinel >>
if 0: # This doesn't work so well in cooperative environments.
    if not at.importing:

        h = headline.strip()

        if h[:5] == "@file":
            i,junk,junk = g.scanAtFileOptions(h)
            fileName = string.strip(h[i:])
            if fileName != at.targetFileName:
                at.readError("File name in @node sentinel does not match file's name")
        elif h[:8] == "@rawfile":
            fileName = string.strip(h[8:])
            if fileName != at.targetFileName:
                at.readError("File name in @node sentinel does not match file's name")
        else:
            at.readError("Missing @file in root @node sentinel")
#@-node:ekr.20041005105605.88:<< Check the filename in the sentinel >>
#@-node:ekr.20041005105605.85:readStartNode (4.x)
#@+node:ekr.20041005105605.89:readStartOthers
def readStartOthers (self,s,i):

    """Read an @+others sentinel."""

    at = self
    j = g.skip_ws(s,i)
    leadingWs = s[i:j]
    if leadingWs:
        assert(g.match(s,j,"@+others"))
    else:
        assert(g.match(s,j,"+others"))

    # Make sure that the generated at-others is properly indented.
    at.out.append(leadingWs + "@others\n")

    at.endSentinelStack.append(at.endOthers)
#@-node:ekr.20041005105605.89:readStartOthers
#@-node:ekr.20041005105605.80:start sentinels
#@+node:ekr.20041005105605.90:end sentinels
#@+node:ekr.20041005105605.91:readEndAll (4.2)
def readEndAll (self,unused_s,unused_i):

    """Read an @-all sentinel."""

    # __pychecker__ = '--no-argsused' # s,i not used, but must be present.

    at = self
    at.popSentinelStack(at.endAll)
#@-node:ekr.20041005105605.91:readEndAll (4.2)
#@+node:ekr.20041005105605.92:readEndAt & readEndDoc
def readEndAt (self,unused_s,unused_i):

    """Read an @-at sentinel."""

    # __pychecker__ = '--no-argsused' # s,i not used, but must be present.

    at = self
    at.readLastDocLine("@")
    at.popSentinelStack(at.endAt)
    at.inCode = True

def readEndDoc (self,unused_s,unused_i):

    """Read an @-doc sentinel."""

    # __pychecker__ = '--no-argsused' # s,i not used, but must be present.

    at = self
    at.readLastDocLine("@doc")
    at.popSentinelStack(at.endDoc)
    at.inCode = True
#@-node:ekr.20041005105605.92:readEndAt & readEndDoc
#@+node:ekr.20041005105605.93:readEndLeo
def readEndLeo (self,unused_s,unused_i):

    """Read an @-leo sentinel."""

    # __pychecker__ = '--no-argsused' # i not used, but must be present.

    at = self

    # Ignore everything after @-leo.
    # Such lines were presumably written by @last.
    while 1:
        s = at.readLine(at.inputFile)
        if len(s) == 0: break
        at.lastLines.append(s) # Capture all trailing lines, even if empty.

    at.done = True
#@-node:ekr.20041005105605.93:readEndLeo
#@+node:ekr.20041005105605.94:readEndMiddle
def readEndMiddle (self,s,i):

    """Read an @-middle sentinel."""

    at = self

    at.readEndNode(s,i,middle=True)
#@-node:ekr.20041005105605.94:readEndMiddle
#@+node:ekr.20041005105605.95:readEndNode (4.x)
def readEndNode (self,unused_s,unused_i,middle=False):

    """Handle end-of-node processing for @-others and @-ref sentinels."""

    # __pychecker__ = '--no-argsused' # s,i not used, but must be present.

    at = self ; c = at.c

    # End raw mode.
    at.raw = False

    # Set the temporary body text.
    s = ''.join(at.out)
    s = g.toUnicode(s,g.app.tkEncoding) # 9/28/03

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

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

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

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

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

if found:
    if 0: # For debugging.
        print ; print '-' * 40
        print "old",len(old)
        for line in g.splitLines(old):
            #line = line.replace(' ','< >').replace('\t','<TAB>')
            print repr(str(line))
        print ; print '-' * 40
        print "new",len(s)
        for line in g.splitLines(s):
            #line = line.replace(' ','< >').replace('\t','<TAB>')
            print repr(str(line))
        print ; print '-' * 40
else:
    # This should never happen.
    g.es("correcting hidden node: t=",repr(at.t),color="red")
#@-node:ekr.20041005105605.97:<< bump at.correctedLines and tell about the correction >>
#@-node:ekr.20041005105605.96:<< indicate that the node has been changed >>
#@-node:ekr.20041005105605.95:readEndNode (4.x)
#@+node:ekr.20041005105605.98:readEndOthers
def readEndOthers (self,unused_s,unused_i):

    """Read an @-others sentinel."""

    # __pychecker__ = '--no-argsused' # s,i unused, but must be present.

    at = self
    at.popSentinelStack(at.endOthers)
#@-node:ekr.20041005105605.98:readEndOthers
#@+node:ekr.20041005105605.99:readLastDocLine
def readLastDocLine (self,tag):

    """Read the @c line that terminates the doc part.
    tag is @doc or @."""

    at = self
    end = at.endSentinelComment
    start = at.startSentinelComment
    s = ''.join(at.docOut)

    # Remove the @doc or @space.  We'll add it back at the end.
    if g.match(s,0,tag):
        s = s[len(tag):]
    else:
        at.readError("Missing start of doc part")
        return

    # Bug fix: Append any whitespace following the tag to tag.
    while s and s[0] in (' ','\t'):
        tag = tag + s[0] ; s = s[1:]

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

    at.out.append(tag + s)
    at.docOut = []
#@-node:ekr.20041005105605.99:readLastDocLine
#@-node:ekr.20041005105605.90:end sentinels
#@+node:ekr.20041005105605.100:Unpaired sentinels
#@+node:ekr.20041005105605.101:ignoreOldSentinel
def  ignoreOldSentinel (self,s,unused_i):

    """Ignore an 3.x sentinel."""

    # __pychecker__ = '--no-argsused' # i unused, but must be present.

    g.es("ignoring 3.x sentinel:",s.strip(),color="blue")
#@-node:ekr.20041005105605.101:ignoreOldSentinel
#@+node:ekr.20041005105605.102:readAfterRef
def  readAfterRef (self,s,i):

    """Read an @afterref sentinel."""

    at = self
    assert(g.match(s,i,"afterref"))

    # Append the next line to the text.
    s = at.readLine(at.inputFile)
    at.out.append(s)
#@-node:ekr.20041005105605.102:readAfterRef
#@+node:ekr.20041005105605.103:readClone
def readClone (self,s,i):

    at = self ; tag = "clone"

    assert(g.match(s,i,tag))

    # Skip the tag and whitespace.
    i = g.skip_ws(s,i+len(tag))

    # Get the clone count.
    junk,val = g.skip_long(s,i)

    if val == None:
        at.readError("Invalid count in @clone sentinel")
    else:
        at.cloneSibCount = val
#@-node:ekr.20041005105605.103:readClone
#@+node:ekr.20041005105605.104:readComment
def readComment (self,s,i):

    """Read an @comment sentinel."""

    assert(g.match(s,i,"comment"))

    # Just ignore the comment line!
#@-node:ekr.20041005105605.104:readComment
#@+node:ekr.20041005105605.105:readDelims
def readDelims (self,s,i):

    """Read an @delims sentinel."""

    at = self
    assert(g.match(s,i-1,"@delims"))

    # Skip the keyword and whitespace.
    i0 = i-1
    i = g.skip_ws(s,i-1+7)

    # Get the first delim.
    j = i
    while i < len(s) and not g.is_ws(s[i]) and not g.is_nl(s,i):
        i += 1

    if j < i:
        at.startSentinelComment = s[j:i]
        # print "delim1:", at.startSentinelComment

        # Get the optional second delim.
        j = i = g.skip_ws(s,i)
        while i < len(s) and not g.is_ws(s[i]) and not g.is_nl(s,i):
            i += 1
        end = g.choose(j<i,s[j:i],"")
        i2 = g.skip_ws(s,i)
        if end == at.endSentinelComment and (i2 >= len(s) or g.is_nl(s,i2)):
            at.endSentinelComment = "" # Not really two params.
            line = s[i0:j]
            line = line.rstrip()
            at.out.append(line+'\n')
        else:
            at.endSentinelComment = end
            # print "delim2:",end
            line = s[i0:i]
            line = line.rstrip()
            at.out.append(line+'\n')
    else:
        at.readError("Bad @delims")
        # Append the bad @delims line to the body text.
        at.out.append("@delims")
#@-node:ekr.20041005105605.105:readDelims
#@+node:ekr.20041005105605.106:readDirective (@@)
def readDirective (self,s,i):

    """Read an @@sentinel."""

    at = self
    assert(g.match(s,i,"@")) # The first '@' has already been eaten.

    # g.trace(g.get_line(s,i))

    if g.match_word(s,i,"@raw"):
        at.raw = True
    elif g.match_word(s,i,"@end_raw"):
        at.raw = False

    e = at.endSentinelComment
    s2 = s[i:]
    if len(e) > 0:
        k = s.rfind(e,i)
        if k != -1:
            s2 = s[i:k] + '\n'

    start = at.startSentinelComment
    if start and len(start) > 0 and start[-1] == '@':
        s2 = s2.replace('@@','@')

    if 0: # New in 4.2.1: never change comment delims here...
        if g.match_word(s,i,"@language"):
            << handle @language >>
        elif g.match_word(s,i,"@comment"):
            << handle @comment >>

    at.out.append(s2)
#@+node:ekr.20041005105605.107:<< handle @language >>
# Skip the keyword and whitespace.
i += len("@language")
i = g.skip_ws(s,i)
j = g.skip_c_id(s,i)
language = s[i:j]

delim1,delim2,delim3 = g.set_delims_from_language(language)

g.trace(g.get_line(s,i))
g.trace(delim1,delim2,delim3)

# Returns a tuple (single,start,end) of comment delims
if delim1:
    at.startSentinelComment = delim1
    at.endSentinelComment = "" # Must not be None.
elif delim2 and delim3:
    at.startSentinelComment = delim2
    at.endSentinelComment = delim3
else:
    line = g.get_line(s,i)
    g.es("ignoring bad @language sentinel:",line,color="red")
#@-node:ekr.20041005105605.107:<< handle @language >>
#@+node:ekr.20041005105605.108:<< handle @comment >>
j = g.skip_line(s,i)
line = s[i:j]
delim1,delim2,delim3 = g.set_delims_from_string(line)

#g.trace(g.get_line(s,i))
#g.trace(delim1,delim2,delim3)

# Returns a tuple (single,start,end) of comment delims
if delim1:
    self.startSentinelComment = delim1
    self.endSentinelComment = "" # Must not be None.
elif delim2 and delim3:
    self.startSentinelComment = delim2
    self.endSentinelComment = delim3
else:
    line = g.get_line(s,i)
    g.es("ignoring bad @comment sentinel:",line,color="red")
#@-node:ekr.20041005105605.108:<< handle @comment >>
#@-node:ekr.20041005105605.106:readDirective (@@)
#@+node:ekr.20041005105605.109:readNl
def readNl (self,s,i):

    """Handle an @nonl sentinel."""

    at = self
    assert(g.match(s,i,"nl"))

    if at.inCode:
        at.out.append('\n')
    else:
        at.docOut.append('\n')
#@-node:ekr.20041005105605.109:readNl
#@+node:ekr.20041005105605.110:readNonl
def readNonl (self,s,i):

    """Handle an @nonl sentinel."""

    at = self
    assert(g.match(s,i,"nonl"))

    if at.inCode:
        s = ''.join(at.out)
        if s and s[-1] == '\n':
            at.out = [s[:-1]]
        else:
            g.trace("out:",s)
            at.readError("unexpected @nonl directive in code part")
    else:
        s = ''.join(at.pending)
        if s:
            if s and s[-1] == '\n':
                at.pending = [s[:-1]]
            else:
                g.trace("docOut:",s)
                at.readError("unexpected @nonl directive in pending doc part")
        else:
            s = ''.join(at.docOut)
            if s and s[-1] == '\n':
                at.docOut = [s[:-1]]
            else:
                g.trace("docOut:",s)
                at.readError("unexpected @nonl directive in doc part")
#@-node:ekr.20041005105605.110:readNonl
#@+node:ekr.20041005105605.111:readRef
@ The sentinel contains an @ followed by a section name in angle brackets.  This code is different from the code for the @@ sentinel: the expansion of the reference does not include a trailing newline.
@c

def readRef (self,s,i):

    """Handle an @<< sentinel."""

    at = self
    j = g.skip_ws(s,i)
    assert(g.match(s,j,"<<"))

    if len(at.endSentinelComment) == 0:
        line = s[i:-1] # No trailing newline
    else:
        k = s.find(at.endSentinelComment,i)
        line = s[i:k] # No trailing newline, whatever k is.

    # Undo the cweb hack.
    start = at.startSentinelComment
    if start and len(start) > 0 and start[-1] == '@':
        line = line.replace('@@','@')

    at.out.append(line)
#@-node:ekr.20041005105605.111:readRef
#@+node:ekr.20041005105605.112:readVerbatim
def readVerbatim (self,s,i):

    """Read an @verbatim sentinel."""

    at = self
    assert(g.match(s,i,"verbatim"))

    # Append the next line to the text.
    s = at.readLine(at.inputFile) 
    i = at.skipIndent(s,0,at.indent)
    at.out.append(s[i:])
#@-node:ekr.20041005105605.112:readVerbatim
#@-node:ekr.20041005105605.100:Unpaired sentinels
#@+node:ekr.20041005105605.113:badEndSentinel, push/popSentinelStack
def badEndSentinel (self,expectedKind):

    """Handle a mismatched ending sentinel."""

    at = self
    assert(at.endSentinelStack)
    s = "Ignoring %s sentinel.  Expecting %s" % (
        at.sentinelName(at.endSentinelStack[-1]),
        at.sentinelName(expectedKind))
    at.readError(s)

def popSentinelStack (self,expectedKind):

    """Pop an entry from endSentinelStack and check it."""

    at = self
    if at.endSentinelStack and at.endSentinelStack[-1] == expectedKind:
        at.endSentinelStack.pop()
    else:
        at.badEndSentinel(expectedKind)
#@-node:ekr.20041005105605.113:badEndSentinel, push/popSentinelStack
#@-node:ekr.20041005105605.74:scanText4 & allies
#@+node:ekr.20041005105605.127:readError
def readError(self,message):

    # This is useful now that we don't print the actual messages.
    if self.errors == 0:
        self.printError("----- read error. line: %s, file: %s" % (
            self.lineNumber,self.targetFileName,))

    # g.trace(self.root,g.callers())
    self.error(message)

    # Bug fix: 12/10/05: Delete all of root's tree.
    self.root.v.t._firstChild = None
    self.root.setOrphan()
    self.root.setDirty()
#@-node:ekr.20041005105605.127:readError
#@-node:ekr.20080315115427.1:give line numbers when errors reading derived files
#@+node:ekr.20080321081209.4:Added support for @bool allow_middle_button_paste
@nocolor

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

And, while you're looking at the editor code :-) - in linux when you paste in
text to a body editor with a middle mouse button click, it's not stored unless
you follow that with a key stroke or some other action in the body editor. If
you instead middle button paste to the editor and then click on another node in
the tree, the text's forgotten.

To do-->@thin ../doc/leoToDoLater.txt-->Can't or won't-->Known Bugs: can't be fixed or can wait-->Bug: can't be fixed-->Cut/paste bug on X windows (waiting for help)

@color
#@nonl
#@+node:ekr.20080324105006.2:Found: Button-
#@+node:ekr.20080324105006.3:No changes needed
#@+node:ekr.20031218072017.3869:tkinterAboutLeo.createFrame
def createFrame (self):

    """Create the frame for an About Leo dialog."""

    if g.app.unitTesting: return

    frame = self.frame
    theCopyright = self.copyright ; email = self.email
    url = self.url ; version = self.version

    # Calculate the approximate height & width. (There are bugs in Tk here.)
    lines = string.split(theCopyright,'\n')
    height = len(lines) + 8 # Add lines for version,url,email,spacing.
    width = 0
    for line in lines:
        width = max(width,len(line))
    width = max(width,len(url))
    width += 10 # 9/9/02

    frame.pack(padx=6,pady=4)

    self.text = w = g.app.gui.plainTextWidget(
        frame,height=height,width=width,bd=0,bg=frame.cget("background"))
    w.pack(pady=10)

    try:
        bitmap_name = g.os_path_join(g.app.loadDir,"..","Icons","Leoapp.GIF") # 5/12/03
        image = Tk.PhotoImage(file=bitmap_name)
        w.image_create("1.0",image=image,padx=10)
    except Exception:
        pass # This can sometimes happen for mysterious reasons.

    w.insert("end",version) #,tag="version")
    w.tag_add('version','end-%dc' %(len(version)+1),'end-1c')
    w.insert("end",theCopyright) #,tag="copyright")
    w.tag_add('copyright','end-%dc' %(len(theCopyright)+1),'end-1c')
    w.insert("end",'\n')
    w.insert("end",url)
    w.tag_add('url','end-%dc' %(len(url)+1),'end-1c')
    w.insert("end",'\n')
    w.insert("end",email)
    w.tag_add('url','end-%dc' %(len(email)+1),'end-1c')

    w.tag_config("version",justify="center")
    w.tag_config("copyright",justify="center",spacing1="3")
    w.tag_config("url",underline=1,justify="center",spacing1="10")

    w.tag_bind("url","<Button-1>",self.onAboutLeoUrl)
    w.tag_bind("url","<Enter>",self.setArrowCursor)
    w.tag_bind("url","<Leave>",self.setDefaultCursor)

    w.tag_config("email",underline=1,justify="center",spacing1="10")
    w.tag_bind("email","<Button-1>",self.onAboutLeoEmail)
    w.tag_bind("email","<Enter>",self.setArrowCursor)
    w.tag_bind("email","<Leave>",self.setDefaultCursor)

    w.configure(state="disabled")
#@-node:ekr.20031218072017.3869:tkinterAboutLeo.createFrame
#@+node:ekr.20031218072017.3890:tkinterListboxDialog.__init__
def __init__ (self,c,title,label):

    """Constructor for the base listboxDialog class."""

    leoTkinterDialog.__init__(self,c,title,resizeable=True) # Initialize the base class.

    if g.app.unitTesting: return

    self.createTopFrame()
    self.top.protocol("WM_DELETE_WINDOW", self.destroy)

    # Initialize common ivars.
    self.label = label
    self.positionList = []
    self.buttonFrame = None

    # Fill in the frame.
    self.createFrame()
    self.fillbox()

    # Make the common bindings after creating self.box.

    self.box.bind("<Double-Button-1>",self.go)
#@-node:ekr.20031218072017.3890:tkinterListboxDialog.__init__
#@+node:ekr.20031218072017.3903:<< Create four columns of radio and checkboxes >>
columnsFrame = Tk.Frame(outer,relief="groove",bd=2)
columnsFrame.pack(anchor="e",expand=1,padx="7p",pady="2p") # Don't fill.

numberOfColumns = 4 # Number of columns
columns = [] ; radioLists = [] ; checkLists = []
for i in xrange(numberOfColumns):
    columns.append(Tk.Frame(columnsFrame,bd=1))
    radioLists.append([])
    checkLists.append([])

for i in xrange(numberOfColumns):
    columns[i].pack(side="left",padx="1p") # fill="y" Aligns to top. padx expands columns.

# HotKeys used for check/radio buttons:  a,b,c,e,h,i,l,m,n,o,p,r,s,t,w

radioLists[0] = [
    (self.svarDict["radio-find-type"],"P&Lain Search","plain-search"),  
    (self.svarDict["radio-find-type"],"&Pattern Match Search","pattern-search"),
    (self.svarDict["radio-find-type"],"&Script Search","script-search")]
checkLists[0] = [
    ("Scrip&t Change",self.svarDict["script_change"])]
checkLists[1] = [
    ("&Whole Word",  self.svarDict["whole_word"]),
    ("&Ignore Case", self.svarDict["ignore_case"]),
    ("Wrap &Around", self.svarDict["wrap"]),
    ("&Reverse",     self.svarDict["reverse"])]
radioLists[2] = [
    (self.svarDict["radio-search-scope"],"&Entire Outline","entire-outline"),
    (self.svarDict["radio-search-scope"],"Suboutline &Only","suboutline-only"),  
    (self.svarDict["radio-search-scope"],"&Node Only","node-only"),
    # I don't know what selection-only is supposed to do.
    (self.svarDict["radio-search-scope"],"Selection Only",None)] #,"selection-only")]
checkLists[2] = []
checkLists[3] = [
    ("Search &Headline Text", self.svarDict["search_headline"]),
    ("Search &Body Text",     self.svarDict["search_body"]),
    ("&Mark Finds",           self.svarDict["mark_finds"]),
    ("Mark &Changes",         self.svarDict["mark_changes"])]

for i in xrange(numberOfColumns):
    for var,name,val in radioLists[i]:
        box = underlinedTkButton("radio",columns[i],anchor="w",text=name,variable=var,value=val)
        box.button.pack(fill="x")
        box.button.bind("<Button-1>", self.resetWrap)
        if val == None: box.button.configure(state="disabled")
        box.bindHotKey(ftxt)
        box.bindHotKey(ctxt)
    for name,var in checkLists[i]:
        box = underlinedTkButton("check",columns[i],anchor="w",text=name,variable=var)
        box.button.pack(fill="x")
        box.button.bind("<Button-1>", self.resetWrap)
        box.bindHotKey(ftxt)
        box.bindHotKey(ctxt)
        if var is None: box.button.configure(state="disabled")
#@nonl
#@-node:ekr.20031218072017.3903:<< Create four columns of radio and checkboxes >>
#@+node:ekr.20060207080537:find.createBindings
def createBindings (self):

    # Legacy bindings.  Can be overwritten in subclasses.

    # g.trace('legacy')

    def findButtonCallback2(event,self=self):
        self.findButton()
        return 'break'

    for widget in (self.find_ctrl, self.change_ctrl):
        widget.bind ("<Button-1>",  self.resetWrap)
        widget.bind("<Key>",        self.resetWrap)
        widget.bind("<Control-a>",  self.selectAllFindText)

    for widget in (self.find_ctrl, self.change_ctrl):
        widget.bind("<Key-Return>", findButtonCallback2)
        widget.bind("<Key-Escape>", self.onCloseWindow)
#@-node:ekr.20060207080537:find.createBindings
#@+node:ekr.20051020120306.17:<< Create two columns of radio and checkboxes >>
columnsFrame = Tk.Frame(outer,relief="groove",bd=2,background=bg)

columnsFrame.pack(expand=0,padx="7p",pady="2p")

numberOfColumns = 2 # Number of columns
columns = [] ; radioLists = [] ; checkLists = []
for i in xrange(numberOfColumns):
    columns.append(Tk.Frame(columnsFrame,bd=1))
    radioLists.append([])
    checkLists.append([])

for i in xrange(numberOfColumns):
    columns[i].pack(side="left",padx="1p") # fill="y" Aligns to top. padx expands columns.

radioLists[0] = []

checkLists[0] = [
    # ("Scrip&t Change",self.svarDict["script_change"]),
    ("Whole &Word", self.svarDict["whole_word"]),
    ("&Ignore Case",self.svarDict["ignore_case"]),
    ("Wrap &Around",self.svarDict["wrap"]),
    ("&Reverse",    self.svarDict["reverse"]),
    ('Rege&xp',     self.svarDict['pattern_match']),
    ("Mark &Finds", self.svarDict["mark_finds"]),
]

radioLists[1] = [
    (self.svarDict["radio-search-scope"],"&Entire Outline","entire-outline"),
    (self.svarDict["radio-search-scope"],"&Suboutline Only","suboutline-only"),  
    (self.svarDict["radio-search-scope"],"&Node Only","node-only"),
]

checkLists[1] = [
    ("Search &Headline", self.svarDict["search_headline"]),
    ("Search &Body",     self.svarDict["search_body"]),
    ("Mark &Changes",    self.svarDict["mark_changes"]),
]

for i in xrange(numberOfColumns):
    for var,name,val in radioLists[i]:
        box = underlinedTkButton(
            "radio",columns[i],anchor="w",text=name,variable=var,value=val,background=bg)
        box.button.pack(fill="x")
        box.button.bind("<Button-1>", self.resetWrap)
        if val == None: box.button.configure(state="disabled")
        box.bindHotKey(ftxt)
        box.bindHotKey(ctxt)
    for name,var in checkLists[i]:
        box = underlinedTkButton(
            "check",columns[i],anchor="w",text=name,variable=var,background=bg)
        box.button.pack(fill="x")
        box.button.bind("<Button-1>", self.resetWrap)
        box.bindHotKey(ftxt)
        box.bindHotKey(ctxt)
        if var is None: box.button.configure(state="disabled")
#@-node:ekr.20051020120306.17:<< Create two columns of radio and checkboxes >>
#@+node:ekr.20051023181449:createBindings (tkFindTab)
def createBindings (self):

    c = self.c ; k = c.k

    def resetWrapCallback(event,self=self,k=k):
        self.resetWrap(event)
        return k.masterKeyHandler(event)

    def findButtonBindingCallback(event=None,self=self):
        self.findButton()
        return 'break'

    table = [
        ('<Button-1>',  k.masterClickHandler),
        ('<Double-1>',  k.masterClickHandler),
        ('<Button-3>',  k.masterClickHandler),
        ('<Double-3>',  k.masterClickHandler),
        ('<Key>',       resetWrapCallback),
        ('<Return>',    findButtonBindingCallback),
        ("<Escape>",    self.hideTab),
    ]

    # table2 = (
        # ('<Button-2>',  self.frame.OnPaste,  k.masterClickHandler),
    # )

    # if c.config.getBool('allow_middle_button_paste'):
        # table.extend(table2)

    for w in (self.find_ctrl,self.change_ctrl):
        for event, callback in table:
            w.bind(event,callback)
#@-node:ekr.20051023181449:createBindings (tkFindTab)
#@+node:ekr.20051025120920:createBindings
def createBindings (self):

    c = self.c ; k = c.k
    widgets = (self.listBox, self.outerFrame)

    for w in widgets:

        # Bind shortcuts for the following commands...
        for commandName,func in (
            ('full-command',            k.fullCommand),
            ('hide-spell-tab',          self.handler.hide),
            ('spell-add',               self.handler.add),
            ('spell-find',              self.handler.find),
            ('spell-ignore',            self.handler.ignore),
            ('spell-change-then-find',  self.handler.changeThenFind),
        ):
            junk, bunchList = c.config.getShortcut(commandName)
            for bunch in bunchList:
                accel = bunch.val
                shortcut = k.shortcutFromSetting(accel)
                if shortcut:
                    # g.trace(shortcut,commandName)
                    w.bind(shortcut,func)

    for binding,func in (
        ("<Double-1>",  self.onChangeThenFindButton),
        ("<Button-1>",  self.onSelectListBox),
        ("<Map>",       self.onMap),
        # These never get called because focus is always in the body pane!
        # ("<Up>",        self.up),
        # ("<Down>",      self.down),
    ):
        self.listBox.bind(binding,func)
#@-node:ekr.20051025120920:createBindings
#@+node:ekr.20041221071131.1:f.createTkTreeCanvas & callbacks
def createTkTreeCanvas (self,parentFrame,scrolls,pack):

    frame = self

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

    treeBar = Tk.Scrollbar(parentFrame,name="treeBar")

    # New in Leo 4.4.3 b1: inject the ivar into the canvas.
    canvas.leo_treeBar = treeBar

    # Bind mouse wheel event to canvas
    if sys.platform != "win32": # Works on 98, crashes on XP.
        canvas.bind("<MouseWheel>", frame.OnMouseWheel)
        if 1: # New in 4.3.
            << workaround for mouse-wheel problems >>

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

    if pack:
        canvas.pack(expand=1,fill="both")

    canvas.bind("<Button-1>", frame.OnActivateTree)

    # Handle mouse wheel in the outline pane.
    if sys.platform == "linux2": # This crashes tcl83.dll
        canvas.bind("<MouseWheel>", frame.OnMouseWheel)

    # g.print_bindings("canvas",canvas)
    return canvas
#@+node:ekr.20050119210541:<< workaround for mouse-wheel problems >>
# Handle mapping of mouse-wheel to buttons 4 and 5.

def mapWheel(e):
    if e.num == 4: # Button 4
        e.delta = 120
        return frame.OnMouseWheel(e)
    elif e.num == 5: # Button 5
        e.delta = -120
        return frame.OnMouseWheel(e)

canvas.bind("<ButtonPress>",mapWheel,add=1)
#@-node:ekr.20050119210541:<< workaround for mouse-wheel problems >>
#@+node:ekr.20031218072017.998:Scrolling callbacks (tkFrame)
def setCallback (self,*args,**keys):

    """Callback to adjust the scrollbar.

    Args is a tuple of two floats describing the fraction of the visible area."""

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

    apply(self.canvas.leo_treeBar.set,args,keys)

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

def yviewCallback (self,*args,**keys):

    """Tell the canvas to scroll"""

    #g.trace(vyiewCallback,args,keys,g.callers())

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

    apply(self.canvas.yview,args,keys)
#@nonl
#@-node:ekr.20031218072017.998:Scrolling callbacks (tkFrame)
#@-node:ekr.20041221071131.1:f.createTkTreeCanvas & callbacks
#@+node:ekr.20031218072017.3961: ctor
def __init__ (self,c,parentFrame):

    self.c = c
    self.colorTags = [] # list of color names used as tags.
    self.enabled = False
    self.isVisible = False
    self.lastRow = self.lastCol = 0
    self.log = c.frame.log
    #if 'black' not in self.log.colorTags:
    #    self.log.colorTags.append("black")
    self.parentFrame = parentFrame
    self.statusFrame = Tk.Frame(parentFrame,bd=2)
    text = "line 0, col 0"
    width = len(text) + 4
    self.labelWidget = Tk.Label(self.statusFrame,text=text,width=width,anchor="w")
    self.labelWidget.pack(side="left",padx=1)

    bg = self.statusFrame.cget("background")
    self.textWidget = w = g.app.gui.bodyTextWidget(
        self.statusFrame,
        height=1,state="disabled",bg=bg,relief="groove",name='status-line')
    self.textWidget.pack(side="left",expand=1,fill="x")
    w.bind("<Button-1>", self.onActivate)
    self.show()

    c.frame.statusFrame = self.statusFrame
    c.frame.statusLabel = self.labelWidget
    c.frame.statusText  = self.textWidget
#@-node:ekr.20031218072017.3961: ctor
#@+node:ekr.20051009044751:createOuterFrames
def createOuterFrames (self):

    f = self ; c = f.c
    f.top = top = Tk.Toplevel()
    g.app.gui.attachLeoIcon(top)
    top.title(f.title)
    top.minsize(30,10) # In grid units.

    if g.os_path_exists(g.app.user_xresources_path):
        f.top.option_readfile(g.app.user_xresources_path)

    f.top.protocol("WM_DELETE_WINDOW", f.OnCloseLeoEvent)
    f.top.bind("<Button-1>", f.OnActivateLeoEvent)

    f.top.bind("<Control-KeyPress>",f.OnControlKeyDown)
    f.top.bind("<Control-KeyRelease>",f.OnControlKeyUp)

    # These don't work on Windows. Because of bugs in window managers,
    # there is NO WAY to know which window is on top!
    # f.top.bind("<Activate>",f.OnActivateLeoEvent)
    # f.top.bind("<Deactivate>",f.OnDeactivateLeoEvent)

    # Create the outer frame, the 'hull' component.
    f.outerFrame = Tk.Frame(top)
    f.outerFrame.pack(expand=1,fill="both")
#@nonl
#@-node:ekr.20051009044751:createOuterFrames
#@+node:ekr.20031218072017.4042:tkLog.createControl
def createControl (self,parentFrame):

    c = self.c

    self.nb = Pmw.NoteBook(parentFrame,
        borderwidth = 1, pagemargin = 0,
        raisecommand = self.raiseTab,
        lowercommand = self.lowerTab,
        arrownavigation = 0,
    )

    menu = self.makeTabMenu(tabName=None)

    def hullMenuCallback(event):
        return self.onRightClick(event,menu)

    self.nb.bind('<Button-3>',hullMenuCallback)

    self.nb.pack(fill='both',expand=1)
    self.selectTab('Log') # Create and activate the default tabs.

    return self.logCtrl
#@-node:ekr.20031218072017.4042:tkLog.createControl
#@+node:ekr.20051022162730:setTabBindings
def setTabBindings (self,tabName):

    c = self.c ; k = c.k
    tab = self.nb.tab(tabName)
    w = self.textDict.get(tabName) or self.frameDict.get(tabName)

    # Send all event in the text area to the master handlers.
    for kind,handler in (
        ('<Key>',       k.masterKeyHandler),
        ('<Button-1>',  k.masterClickHandler),
        ('<Button-3>',  k.masterClick3Handler),
    ):
        w.bind(kind,handler)

    # Clicks in the tab area are harmless: use the old code.
    def tabMenuRightClickCallback(event,menu=self.menu):
        return self.onRightClick(event,menu)

    def tabMenuClickCallback(event,tabName=tabName):
        return self.onClick(event,tabName)

    tab.bind('<Button-1>',tabMenuClickCallback)
    tab.bind('<Button-3>',tabMenuRightClickCallback)

    k.completeAllBindingsForWidget(w)
#@-node:ekr.20051022162730:setTabBindings
#@+node:ekr.20071003090546:setCanvasTabBindings
def setCanvasTabBindings (self,tabName,menu):

    c = self.c ; tab = self.nb.tab(tabName)

    def tabMenuRightClickCallback(event,menu=menu):
        return self.onRightClick(event,menu)

    def tabMenuClickCallback(event,tabName=tabName):
        return self.onClick(event,tabName)

    tab.bind('<Button-1>',tabMenuClickCallback)
    tab.bind('<Button-3>',tabMenuRightClickCallback)

#@-node:ekr.20071003090546:setCanvasTabBindings
#@+node:ekr.20060726133852:createBindings (fontPicker)
def createBindings (self):

    c = self.c ; k = c.k

    table = (
        ('<Button-1>',  k.masterClickHandler),
        ('<Double-1>',  k.masterClickHandler),
        ('<Button-3>',  k.masterClickHandler),
        ('<Double-3>',  k.masterClickHandler),
        ('<Key>',       k.masterKeyHandler),
        ("<Escape>",    self.hideFontTab),
    )

    w = self.sampleWidget
    for event, callback in table:
        w.bind(event,callback)

    k.completeAllBindingsForWidget(w)
#@-node:ekr.20060726133852:createBindings (fontPicker)
#@+node:ekr.20060621164312:makeScriptButton
def makeScriptButton (self,c,
    p=None, # A node containing the script.
    script=None, # The script itself.
    buttonText=None,
    balloonText='Script Button',
    shortcut=None,bg='LightSteelBlue1',
    define_g=True,define_name='__main__',silent=False, # Passed on to c.executeScript.
):

    '''Create a script button for the script in node p.
    The button's text defaults to p.headString'''

    k = c.k
    if p and not buttonText: buttonText = p.headString().strip()
    if not buttonText: buttonText = 'Unnamed Script Button'
    << create the button b >>
    << define the callbacks for b >>
    b.configure(command=executeScriptCallback)
    b.bind('<Button-3>',deleteButtonCallback)
    if shortcut:
        << bind the shortcut to executeScriptCallback >>
    << create press-buttonText-button command >>
#@+node:ekr.20060621164312.1:<< create the button b >>
iconBar = c.frame.getIconBarObject()
b = iconBar.add(text=buttonText)

if balloonText and balloonText != buttonText:
    Pmw = g.importExtension('Pmw',pluginName='gui.makeScriptButton',verbose=False)
    if Pmw:
        balloon = Pmw.Balloon(b,initwait=100)
        balloon.bind(b,balloonText)

if sys.platform == "win32":
    width = int(len(buttonText) * 0.9)
    b.configure(width=width,font=('verdana',7,'bold'),bg=bg)
#@-node:ekr.20060621164312.1:<< create the button b >>
#@+node:ekr.20060621164312.2:<< define the callbacks for b >>
def deleteButtonCallback(event=None,b=b,c=c):
    if b: b.pack_forget()
    c.bodyWantsFocus()

def executeScriptCallback (event=None,
    b=b,c=c,buttonText=buttonText,p=p and p.copy(),script=script):

    if c.disableCommandsMessage:
        g.es('',c.disableCommandsMessage,color='blue')
    else:
        g.app.scriptDict = {}
        c.executeScript(p=p,script=script,
        define_g= define_g,define_name=define_name,silent=silent)
        # Remove the button if the script asks to be removed.
        if g.app.scriptDict.get('removeMe'):
            g.es("removing","'%s'" % (buttonText),"button at its request")
            b.pack_forget()
    # Do not assume the script will want to remain in this commander.
#@-node:ekr.20060621164312.2:<< define the callbacks for b >>
#@+node:ekr.20060621164312.3:<< bind the shortcut to executeScriptCallback >>
func = executeScriptCallback
shortcut = k.canonicalizeShortcut(shortcut)
ok = k.bindKey ('button', shortcut,func,buttonText)
if ok:
    g.es_print('bound @button',buttonText,'to',shortcut,color='blue')
#@-node:ekr.20060621164312.3:<< bind the shortcut to executeScriptCallback >>
#@+node:ekr.20060621164312.4:<< create press-buttonText-button command >>
aList = [g.choose(ch.isalnum(),ch,'-') for ch in buttonText]

buttonCommandName = ''.join(aList)
buttonCommandName = buttonCommandName.replace('--','-')
buttonCommandName = 'press-%s-button' % buttonCommandName.lower()

# This will use any shortcut defined in an @shortcuts node.
k.registerCommand(buttonCommandName,None,executeScriptCallback,pane='button',verbose=False)
#@-node:ekr.20060621164312.4:<< create press-buttonText-button command >>
#@-node:ekr.20060621164312:makeScriptButton
#@+node:ekr.20070327103016:tkTree.setCanvasBindings
def setCanvasBindings (self,canvas):

    k = self.c.k

    canvas.bind('<Key>',k.masterKeyHandler)
    canvas.bind('<Button-1>',self.onTreeClick)

    << make bindings for tagged items on the canvas >>
    << create baloon bindings for tagged items on the canvas >>
#@nonl
#@+node:ekr.20060131173440.2:<< make bindings for tagged items on the canvas >>
where = g.choose(self.expanded_click_area,'clickBox','plusBox')

table = (
    (where,    '<Button-1>',self.onClickBoxClick),
    ('iconBox','<Button-1>',self.onIconBoxClick),
    ('iconBox','<Double-1>',self.onIconBoxDoubleClick),
    ('iconBox','<Button-3>',self.onIconBoxRightClick),
    ('iconBox','<Double-3>',self.onIconBoxRightClick),
    ('iconBox','<B1-Motion>',self.onDrag),
    ('iconBox','<Any-ButtonRelease-1>',self.onEndDrag),
)
for tag,event,callback in table:
    canvas.tag_bind(tag,event,callback)
#@-node:ekr.20060131173440.2:<< make bindings for tagged items on the canvas >>
#@+node:ekr.20060307080642:<< create baloon bindings for tagged items on the canvas >>
if 0: # I find these very irritating.
    for tag,text in (
        # ('plusBox','plusBox'),
        ('iconBox','Icon Box'),
        ('selectBox','Click to select'),
        ('clickBox','Click to expand or contract'),
        # ('textBox','Headline'),
    ):
        # A fairly long wait is best.
        balloon = Pmw.Balloon(self.canvas,initwait=700)
        balloon.tagbind(self.canvas,tag,balloonHelp=text)
#@-node:ekr.20060307080642:<< create baloon bindings for tagged items on the canvas >>
#@-node:ekr.20070327103016:tkTree.setCanvasBindings
#@+node:ekr.20050618045715:<< patch by Maciej Kalisiak  to handle scroll-wheel events >>
def PropagateButton4(e):
    canvas.event_generate("<Button-4>")
    return "break"

def PropagateButton5(e):
    canvas.event_generate("<Button-5>")
    return "break"

def PropagateMouseWheel(e):
    canvas.event_generate("<MouseWheel>")
    return "break"

instance_tag = w.bindtags()[0]
w.bind_class(instance_tag, "<Button-4>", PropagateButton4)
w.bind_class(instance_tag, "<Button-5>", PropagateButton5)
w.bind_class(instance_tag, "<MouseWheel>",PropagateMouseWheel)
#@-node:ekr.20050618045715:<< patch by Maciej Kalisiak  to handle scroll-wheel events >>
#@+node:ekr.20060131173440.2:<< make bindings for tagged items on the canvas >>
where = g.choose(self.expanded_click_area,'clickBox','plusBox')

table = (
    (where,    '<Button-1>',self.onClickBoxClick),
    ('iconBox','<Button-1>',self.onIconBoxClick),
    ('iconBox','<Double-1>',self.onIconBoxDoubleClick),
    ('iconBox','<Button-3>',self.onIconBoxRightClick),
    ('iconBox','<Double-3>',self.onIconBoxRightClick),
    ('iconBox','<B1-Motion>',self.onDrag),
    ('iconBox','<Any-ButtonRelease-1>',self.onEndDrag),
)
for tag,event,callback in table:
    canvas.tag_bind(tag,event,callback)
#@-node:ekr.20060131173440.2:<< make bindings for tagged items on the canvas >>
#@-node:ekr.20080324105006.3:No changes needed
#@+node:ekr.20080324105006.4:Changed  B1-ButtonRelease to Button-1-ButtonRelease
#@+node:ekr.20040803072955.18:<< old ivars >>
# Miscellaneous info.
self.iconimages = {} # Image cache set by getIconImage().
self.active = False # True if present headline is active
self._editPosition = None # Returned by leoTree.editPosition()
self.lineyoffset = 0 # y offset for this headline.
self.lastClickFrameId = None # id of last entered clickBox.
self.lastColoredText = None # last colored text widget.

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

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

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

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

if self.allocateOnlyVisibleNodes:
    self.frame.bar1.bind("<Button-1-ButtonRelease>", self.redraw_now)
#@-node:ekr.20040803072955.18:<< old ivars >>
#@-node:ekr.20080324105006.4:Changed  B1-ButtonRelease to Button-1-ButtonRelease
#@+node:ekr.20080324105006.7:Changed
#@+node:ekr.20031218072017.838:tkBody.createBindings
def createBindings (self,w=None):

    '''(tkBody) Create gui-dependent bindings.
    These are *not* made in nullBody instances.'''

    frame = self.frame ; c = self.c ; k = c.k
    if not w: w = self.bodyCtrl

    w.bind('<Key>', k.masterKeyHandler)

    table = [
        ('<Button-1>',  frame.OnBodyClick,          k.masterClickHandler),
        ('<Button-3>',  frame.OnBodyRClick,         k.masterClick3Handler),
        ('<Double-1>',  frame.OnBodyDoubleClick,    k.masterDoubleClickHandler),
        ('<Double-3>',  None,                       k.masterDoubleClick3Handler),
        ('<Button-2>',  frame.OnPaste,              k.masterClickHandler),
    ]

    table2 = (
        ('<Button-2>',  frame.OnPaste,              k.masterClickHandler),
    )

    if c.config.getBool('allow_middle_button_paste'):
        table.extend(table2)

    for kind,func,handler in table:
        def bodyClickCallback(event,handler=handler,func=func):
            return handler(event,func)

        w.bind(kind,bodyClickCallback)
#@-node:ekr.20031218072017.838:tkBody.createBindings
#@+node:ekr.20051024102724:tkTtree.setBindings & helper
def setBindings (self,):

    '''Create master bindings for all headlines.'''

    tree = self ; k = self.c.k

    # g.trace('self',self,'canvas',self.canvas)

    tree.setBindingsHelper()

    tree.setCanvasBindings(self.canvas)

    k.completeAllBindingsForWidget(self.canvas)

    k.completeAllBindingsForWidget(self.bindingWidget)

#@+node:ekr.20060131173440:tkTree.setBindingsHelper
def setBindingsHelper (self):

    tree = self ; k = self.c.k

    self.bindingWidget = w = g.app.gui.plainTextWidget(
        self.canvas,name='bindingWidget')

    w.bind('<Key>',k.masterKeyHandler)

    table = [
        ('<Button-1>',       k.masterClickHandler,          tree.onHeadlineClick),
        ('<Button-3>',       k.masterClick3Handler,         tree.onHeadlineRightClick),
        ('<Double-Button-1>',k.masterDoubleClickHandler,    tree.onHeadlineClick),
        ('<Double-Button-3>',k.masterDoubleClick3Handler,   tree.onHeadlineRightClick),
    ]

    for a,handler,func in table:
        def treeBindingCallback(event,handler=handler,func=func):
            # g.trace('func',func)
            return handler(event,func)
        w.bind(a,treeBindingCallback)

    self.textBindings = w.bindtags()
#@-node:ekr.20060131173440:tkTree.setBindingsHelper
#@-node:ekr.20051024102724:tkTtree.setBindings & helper
#@+node:ekr.20060203114017:f.setMinibufferBindings
def setMinibufferBindings (self):

    '''Create bindings for the minibuffer..'''

    f = self ; c = f.c ; k = c.k ; w = f.miniBufferWidget

    if not c.useTextMinibuffer: return

    table = [
        ('<Key>',           k.masterKeyHandler),
        ('<Button-1>',      k.masterClickHandler),
        ('<Button-3>',      k.masterClick3Handler),
        ('<Double-1>',      k.masterDoubleClickHandler),
        ('<Double-3>',      k.masterDoubleClick3Handler),
    ]

    table2 = (
        ('<Button-2>',      k.masterClickHandler),
    )

    if c.config.getBool('allow_middle_button_paste'):
        table.extend(table2)

    for kind,callback in table:
        w.bind(kind,callback)

    if 0:
        if sys.platform.startswith('win'):
            # Support Linux middle-button paste easter egg.
            w.bind("<Button-2>",f.OnPaste)
#@-node:ekr.20060203114017:f.setMinibufferBindings
#@+node:ekr.20051023181449:createBindings (tkFindTab)
def createBindings (self):

    c = self.c ; k = c.k

    def resetWrapCallback(event,self=self,k=k):
        self.resetWrap(event)
        return k.masterKeyHandler(event)

    def findButtonBindingCallback(event=None,self=self):
        self.findButton()
        return 'break'

    table = [
        ('<Button-1>',  k.masterClickHandler),
        ('<Double-1>',  k.masterClickHandler),
        ('<Button-3>',  k.masterClickHandler),
        ('<Double-3>',  k.masterClickHandler),
        ('<Key>',       resetWrapCallback),
        ('<Return>',    findButtonBindingCallback),
        ("<Escape>",    self.hideTab),
    ]

    # table2 = (
        # ('<Button-2>',  self.frame.OnPaste,  k.masterClickHandler),
    # )

    # if c.config.getBool('allow_middle_button_paste'):
        # table.extend(table2)

    for w in (self.find_ctrl,self.change_ctrl):
        for event, callback in table:
            w.bind(event,callback)
#@-node:ekr.20051023181449:createBindings (tkFindTab)
#@-node:ekr.20080324105006.7:Changed
#@-node:ekr.20080324105006.2:Found: Button-
#@-node:ekr.20080321081209.4:Added support for @bool allow_middle_button_paste
#@+node:ekr.20080326052859.1:Bobjack's changes
#@+node:bobjack.20080324141020.4:doPopup & helper
def doPopup (self,p,kind,name,val):

    """
    Handle @popup menu items in @settings trees.
    """

    # __pychecker__ = '--no-argsused' # kind, not used.

    popupName = name
    popupType = val

    c = self.c ; aList = [] ; tag = '@menu'

    #g.trace(p, kind, name, val, c)

    aList = []
    p = p.copy()
    self.doPopupItems(p,aList)


    if not hasattr(g.app.config, 'context_menus'):
        g.app.config.context_menus = {}

    if popupName in g.app.config.context_menus:
        print '*** duplicate popup ***', popupName

    g.app.config.context_menus[popupName] = aList
#@+node:bobjack.20080324141020.5:doPopupItems
def doPopupItems (self,p,aList):

    p = p.copy() ; after = p.nodeAfterTree()
    p.moveToThreadNext()
    while p and p != after:
        h = p.headString()
        for tag in ('@menu','@item'):
            if g.match_word(h,0,tag):
                itemName = h[len(tag):].strip()
                if itemName:
                    if tag == '@menu':
                        aList2 = []
                        kind = '%s' % itemName
                        self.doPopupItems(p,aList2)
                        aList.append((kind,aList2),)
                        p.moveToNodeAfterTree()
                        break
                    else:
                        kind = tag
                        head = itemName
                        body = p.bodyString()
                        aList.append((head,body),)
                        p.moveToThreadNext()
                        break
        else:
            # g.trace('***skipping***',p.headString())
            p.moveToThreadNext()
#@nonl
#@-node:bobjack.20080324141020.5:doPopupItems
#@-node:bobjack.20080324141020.4:doPopup & helper
#@-node:ekr.20080326052859.1:Bobjack's changes
#@+node:ekr.20080327061021.230:Improved scripting docs & docstrings
@nocolor

- Add a few more words about getBool to Leo's scripting chapter: the default arg should be mentioned.

- Add docstrings to g.app.getX methods.

@color
#@nonl
#@+node:ekr.20041120094940:kind handlers (parserBaseClass)
#@+node:ekr.20060608221203:doAbbrev
def doAbbrev (self,p,kind,name,val):

    d = {}
    s = p.bodyString()
    lines = g.splitLines(s)
    for line in lines:
        line = line.strip()
        if line and not g.match(line,0,'#'):
            name,val = self.parseAbbrevLine(line)
            if name: d [val] = name

    self.set (p,'abbrev','abbrev',d)
#@-node:ekr.20060608221203:doAbbrev
#@+node:ekr.20041120094940.1:doBool
def doBool (self,p,kind,name,val):

    if val in ('True','true','1'):
        self.set(p,kind,name,True)
    elif val in ('False','false','0'):
        self.set(p,kind,name,False)
    else:
        self.valueError(p,kind,name,val)
#@-node:ekr.20041120094940.1:doBool
#@+node:ekr.20070925144337:doButtons
def doButtons (self,p,kind,name,val):

    '''Handle an @buttons tree.'''

    # __pychecker__ = '--no-argsused' # kind,name,val not used.

    aList = [] ; c = self.c ; tag = '@button'
    for p in p.subtree_iter():
        h = p.headString()
        if g.match_word(h,0,tag):
            # We can not assume that p will be valid when it is used.
            script = g.getScript(c,p,useSelectedText=False,forcePythonSentinels=True,useSentinels=True)
            aList.append((p.headString(),script),)

    # g.trace(g.listToString([h for h,script in aList]))

    # This setting is handled differently from most other settings,
    # because the last setting must be retrieved before any commander exists.
    g.app.config.atCommonButtonsList = aList
    g.app.config.buttonsFileName = c and c.shortFileName() or '<no settings file>'

#@-node:ekr.20070925144337:doButtons
#@+node:ekr.20080312071248.6:doCommands
def doCommands (self,p,kind,name,val):

    '''Handle an @commands tree.'''

    # __pychecker__ = '--no-argsused' # kind,name,val not used.

    aList = [] ; c = self.c ; tag = '@command'
    for p in p.subtree_iter():
        h = p.headString()
        if g.match_word(h,0,tag):
            # We can not assume that p will be valid when it is used.
            script = g.getScript(c,p,useSelectedText=False,forcePythonSentinels=True,useSentinels=True)
            aList.append((p.headString(),script),)

    # g.trace(g.listToString(aList))

    # This setting is handled differently from most other settings,
    # because the last setting must be retrieved before any commander exists.
    g.app.config.atCommonCommandsList = aList


#@-node:ekr.20080312071248.6:doCommands
#@+node:ekr.20041120094940.2:doColor
def doColor (self,p,kind,name,val):

    # At present no checking is done.
    val = val.lstrip('"').rstrip('"')
    val = val.lstrip("'").rstrip("'")

    self.set(p,kind,name,val)
#@-node:ekr.20041120094940.2:doColor
#@+node:ekr.20071214140900:doData
def doData (self,p,kind,name,val):

    s = p.bodyString()
    lines = g.splitLines(s)
    data = [z.strip() for z in lines if z.strip() and not z.startswith('#')]

    self.set(p,kind,name,data)
#@-node:ekr.20071214140900:doData
#@+node:ekr.20041120094940.3:doDirectory & doPath
def doDirectory (self,p,kind,name,val):

    # At present no checking is done.
    self.set(p,kind,name,val)

doPath = doDirectory
#@-node:ekr.20041120094940.3:doDirectory & doPath
#@+node:ekr.20070224075914:doEnabledPlugins
def doEnabledPlugins (self,p,kind,name,val):

    # __pychecker__ = '--no-argsused' # kind,name,val not used.

    c = self.c
    s = p.bodyString()

    # This setting is handled differently from all other settings,
    # because the last setting must be retrieved before any commander exists.

    # g.trace('len(s)',len(s))

    # Set the global config ivars.
    g.app.config.enabledPluginsString = s
    g.app.config.enabledPluginsFileName = c and c.shortFileName() or '<no settings file>'
#@-node:ekr.20070224075914:doEnabledPlugins
#@+node:ekr.20041120094940.6:doFloat
def doFloat (self,p,kind,name,val):

    try:
        val = float(val)
        self.set(p,kind,name,val)
    except ValueError:
        self.valueError(p,kind,name,val)
#@-node:ekr.20041120094940.6:doFloat
#@+node:ekr.20041120094940.4:doFont
def doFont (self,p,kind,name,val):

    # __pychecker__ = '--no-argsused' # kind not used.

    d = self.parseFont(p)

    # Set individual settings.
    for key in ('family','size','slant','weight'):
        data = d.get(key)
        if data is not None:
            name,val = data
            setKind = key
            self.set(p,setKind,name,val)
#@-node:ekr.20041120094940.4:doFont
#@+node:ekr.20041120103933:doIf
def doIf(self,p,kind,name,val):

    # __pychecker__ = '--no-argsused' # args not used.

    g.trace("'if' not supported yet")
    return None
#@-node:ekr.20041120103933:doIf
#@+node:ekr.20041121125416:doIfGui
@ Alas, @if-gui can't be made to work. The problem is that plugins can set
g.app.gui, but plugins need settings so the leoSettings.leo files must be parsed
before g.app.gui.guiName() is known.
@c

if 0:

    def doIfGui (self,p,kind,name,val):

        # __pychecker__ = '--no-argsused' # args not used.

        # g.trace(repr(name))

        if not g.app.gui or not g.app.gui.guiName():
            s = '@if-gui has no effect: g.app.gui not defined yet'
            g.es_print(s,color='blue')
            return "skip"
        elif g.app.gui.guiName().lower() == name.lower():
            return None
        else:
            return "skip"
#@-node:ekr.20041121125416:doIfGui
#@+node:ekr.20041120104215:doIfPlatform
def doIfPlatform (self,p,kind,name,val):

    # __pychecker__ = '--no-argsused' # args not used.

    # g.trace(sys.platform,repr(name))

    if sys.platform.lower() == name.lower():
        return None
    else:
        return "skip"
#@-node:ekr.20041120104215:doIfPlatform
#@+node:ekr.20041120104215.1:doIgnore
def doIgnore(self,p,kind,name,val):

    return "skip"
#@-node:ekr.20041120104215.1:doIgnore
#@+node:ekr.20041120094940.5:doInt
def doInt (self,p,kind,name,val):

    try:
        val = int(val)
        self.set(p,kind,name,val)
    except ValueError:
        self.valueError(p,kind,name,val)
#@-node:ekr.20041120094940.5:doInt
#@+node:ekr.20041217132253:doInts
def doInts (self,p,kind,name,val):

    '''We expect either:
    @ints [val1,val2,...]aName=val
    @ints aName[val1,val2,...]=val'''

    name = name.strip() # The name indicates the valid values.
    i = name.find('[')
    j = name.find(']')

    # g.trace(kind,name,val)

    if -1 < i < j:
        items = name[i+1:j]
        items = items.split(',')
        name = name[:i]+name[j+1:].strip()
        # g.trace(name,items)
        try:
            items = [int(item.strip()) for item in items]
        except ValueError:
            items = []
            self.valueError(p,'ints[]',name,val)
            return
        kind = "ints[%s]" % (','.join([str(item) for item in items]))
        try:
            val = int(val)
        except ValueError:
            self.valueError(p,'int',name,val)
            return
        if val not in items:
            self.error("%d is not in %s in %s" % (val,kind,name))
            return

        # g.trace(repr(kind),repr(name),val)

        # At present no checking is done.
        self.set(p,kind,name,val)
#@-node:ekr.20041217132253:doInts
#@+node:ekr.20070925144337.2:doMenus & helper
def doMenus (self,p,kind,name,val):

    # __pychecker__ = '--no-argsused' # kind,name,val not used.

    c = self.c ; aList = [] ; tag = '@menu'
    p = p.copy() ; after = p.nodeAfterTree()
    while p and p != after:
        h = p.headString()
        if g.match_word(h,0,tag):
            name = h[len(tag):].strip()
            if name:
                for z in aList:
                    name2,junk,junk = z
                    if name2 == name:
                        self.error('Replacing previous @menu %s' % (name))
                        break
                aList2 = []
                kind = '%s %s' % (tag,name)
                self.doItems(p,aList2)
                aList.append((kind,aList2,None),)
                p.moveToNodeAfterTree()
            else:
                p.moveToThreadNext()
        else:
            p.moveToThreadNext()

    # This setting is handled differently from most other settings,
    # because the last setting must be retrieved before any commander exists.
    # self.dumpMenuList(aList)
    # g.trace(g.listToString(aList))
    # g.es_print('creating menu from',c.shortFileName(),color='blue')
    g.app.config.menusList = aList
    g.app.config.menusFileName = c and c.shortFileName() or '<no settings file>'
#@+node:ekr.20070926141716:doItems
def doItems (self,p,aList):

    p = p.copy() ; after = p.nodeAfterTree()
    p.moveToThreadNext()
    while p and p != after:
        h = p.headString()
        for tag in ('@menu','@item'):
            if g.match_word(h,0,tag):
                itemName = h[len(tag):].strip()
                if itemName:
                    if tag == '@menu':
                        aList2 = []
                        kind = '%s %s' % (tag,itemName)
                        self.doItems(p,aList2)
                        aList.append((kind,aList2,None),)
                        p.moveToNodeAfterTree()
                        break
                    else:
                        kind = tag
                        head = itemName
                        body = p.bodyString()
                        aList.append((kind,head,body),)
                        p.moveToThreadNext()
                        break
        else:
            # g.trace('***skipping***',p.headString())
            p.moveToThreadNext()
#@nonl
#@-node:ekr.20070926141716:doItems
#@+node:ekr.20070926142312:dumpMenuList
def dumpMenuList (self,aList,level=0):

    for z in aList:
        kind,val,val2 = z
        if kind == '@item':
            g.trace(level,kind,val,val2)
        else:
            print
            g.trace(level,kind,'...')
            self.dumpMenuList(val,level+1)
#@nonl
#@-node:ekr.20070926142312:dumpMenuList
#@-node:ekr.20070925144337.2:doMenus & helper
#@+node:ekr.20060102103625.1:doMode (ParserBaseClass)
def doMode(self,p,kind,name,val):

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

    # __pychecker__ = '--no-argsused' # val not used.

    c = self.c ; k = c.k

    # g.trace('%20s' % (name),c.fileName())
    << Compute modeName >>

    # Create a local shortcutsDict.
    old_d = self.shortcutsDict
    d = self.shortcutsDict = {}

    s = p.bodyString()
    lines = g.splitLines(s)
    for line in lines:
        line = line.strip()
        if line and not g.match(line,0,'#'):
            name,bunch = self.parseShortcutLine(line)
            if not name:
                # An entry command: put it in the special *entry-commands* key.
                aList = d.get('*entry-commands*',[])
                aList.append(bunch.entryCommandName)
                d ['*entry-commands*'] = aList
            elif bunch is not None:
                # A regular shortcut.
                bunch.val = k.strokeFromSetting(bunch.val)
                bunch.pane = modeName
                bunchList = d.get(name,[])
                # Important: use previous bindings if possible.
                key2,bunchList2 = c.config.getShortcut(name)
                bunchList3 = [b for b in bunchList2 if b.pane != modeName]
                if bunchList3:
                    # g.trace('inheriting',[b.val for b in bunchList3])
                    bunchList.extend(bunchList3)
                bunchList.append(bunch)
                d [name] = bunchList
                self.set(p,"shortcut",name,bunchList)
                self.setShortcut(name,bunchList)

    # Restore the global shortcutsDict.
    self.shortcutsDict = old_d

    # Create the command, but not any bindings to it.
    self.createModeCommand(modeName,d)
#@+node:ekr.20060618110649:<< Compute modeName >>
name = name.strip().lower()
j = name.find(' ')
if j > -1: name = name[:j]
if name.endswith('mode'):
    name = name[:-4].strip()
if name.endswith('-'):
    name = name[:-1]
modeName = name + '-mode'
#@-node:ekr.20060618110649:<< Compute modeName >>
#@-node:ekr.20060102103625.1:doMode (ParserBaseClass)
#@+node:ekr.20070411101643.1:doOpenWith (ParserBaseClass)
def doOpenWith (self,p,kind,name,val):

    # g.trace('kind',kind,'name',name,'val',val,'c',self.c)

    d = self.parseOpenWith(p)
    d['name']=name
    d['shortcut']=val
    name = kind = 'openwithtable'
    self.openWithList.append(d)
    self.set(p,kind,name,self.openWithList)
#@-node:ekr.20070411101643.1:doOpenWith (ParserBaseClass)
#@+node:ekr.20041120104215.2:doPage
def doPage(self,p,kind,name,val):

    pass # Ignore @page this while parsing settings.
#@-node:ekr.20041120104215.2:doPage
#@+node:ekr.20041121125741:doRatio
def doRatio (self,p,kind,name,val):

    try:
        val = float(val)
        if 0.0 <= val <= 1.0:
            self.set(p,kind,name,val)
        else:
            self.valueError(p,kind,name,val)
    except ValueError:
        self.valueError(p,kind,name,val)
#@-node:ekr.20041121125741:doRatio
#@+node:ekr.20041120105609:doShortcuts (ParserBaseClass)
def doShortcuts(self,p,kind,name,val,s=None):

    # __pychecker__ = '--no-argsused' # kind,val.

    # g.trace(self.c.fileName(),name)

    c = self.c ; d = self.shortcutsDict
    if s is None: s = p.bodyString()
    lines = g.splitLines(s)
    for line in lines:
        line = line.strip()
        if line and not g.match(line,0,'#'):
            name,bunch = self.parseShortcutLine(line)
            if bunch is not None:
                # A regular shortcut.
                bunchList = d.get(name,[])
                bunchList.append(bunch)
                d [name] = bunchList
                self.set(p,"shortcut",name,bunchList)
                self.setShortcut(name,bunchList)
#@-node:ekr.20041120105609:doShortcuts (ParserBaseClass)
#@+node:ekr.20041217132028:doString
def doString (self,p,kind,name,val):

    # At present no checking is done.
    self.set(p,kind,name,val)
#@-node:ekr.20041217132028:doString
#@+node:ekr.20041120094940.8:doStrings
def doStrings (self,p,kind,name,val):

    '''We expect one of the following:
    @strings aName[val1,val2...]=val
    @strings [val1,val2,...]aName=val'''

    name = name.strip()
    i = name.find('[')
    j = name.find(']')

    if -1 < i < j:
        items = name[i+1:j]
        items = items.split(',')
        items = [item.strip() for item in items]
        name = name[:i]+name[j+1:].strip()
        kind = "strings[%s]" % (','.join(items))
        # g.trace(repr(kind),repr(name),val)

        # At present no checking is done.
        self.set(p,kind,name,val)
#@-node:ekr.20041120094940.8:doStrings
#@+node:bobjack.20080324141020.4:doPopup & helper
def doPopup (self,p,kind,name,val):

    """
    Handle @popup menu items in @settings trees.
    """

    # __pychecker__ = '--no-argsused' # kind, not used.

    popupName = name
    popupType = val

    c = self.c ; aList = [] ; tag = '@menu'

    #g.trace(p, kind, name, val, c)

    aList = []
    p = p.copy()
    self.doPopupItems(p,aList)


    if not hasattr(g.app.config, 'context_menus'):
        g.app.config.context_menus = {}

    if popupName in g.app.config.context_menus:
        print '*** duplicate popup ***', popupName

    g.app.config.context_menus[popupName] = aList
#@+node:bobjack.20080324141020.5:doPopupItems
def doPopupItems (self,p,aList):

    p = p.copy() ; after = p.nodeAfterTree()
    p.moveToThreadNext()
    while p and p != after:
        h = p.headString()
        for tag in ('@menu','@item'):
            if g.match_word(h,0,tag):
                itemName = h[len(tag):].strip()
                if itemName:
                    if tag == '@menu':
                        aList2 = []
                        kind = '%s' % itemName
                        self.doPopupItems(p,aList2)
                        aList.append((kind,aList2),)
                        p.moveToNodeAfterTree()
                        break
                    else:
                        kind = tag
                        head = itemName
                        body = p.bodyString()
                        aList.append((head,body),)
                        p.moveToThreadNext()
                        break
        else:
            # g.trace('***skipping***',p.headString())
            p.moveToThreadNext()
#@nonl
#@-node:bobjack.20080324141020.5:doPopupItems
#@-node:bobjack.20080324141020.4:doPopup & helper
#@-node:ekr.20041120094940:kind handlers (parserBaseClass)
#@+node:ekr.20041213082558:parsers
#@+node:ekr.20041213083651:fontSettingNameToFontKind
def fontSettingNameToFontKind (self,name):

    s = name.strip()
    if s:
        for tag in ('_family','_size','_slant','_weight'):
            if s.endswith(tag):
                return tag[1:]

    return None
#@-node:ekr.20041213083651:fontSettingNameToFontKind
#@+node:ekr.20041213082558.1:parseFont & helper
def parseFont (self,p):

    d = {
        'comments': [],
        'family': None,
        'size': None,
        'slant': None,
        'weight': None,
    }

    s = p.bodyString()
    lines = g.splitLines(s)

    for line in lines:
        self.parseFontLine(line,d)

    comments = d.get('comments')
    d['comments'] = '\n'.join(comments)

    return d
#@+node:ekr.20041213082558.2:parseFontLine
def parseFontLine (self,line,d):

    s = line.strip()
    if not s: return

    try:
        s = str(s)
    except UnicodeError:
        pass

    if g.match(s,0,'#'):
        s = s[1:].strip()
        comments = d.get('comments')
        comments.append(s)
        d['comments'] = comments
    else:
        # name is everything up to '='
        i = s.find('=')
        if i == -1:
            name = s ; val = None
        else:
            name = s[:i].strip()
            val = s[i+1:].strip()
            val = val.lstrip('"').rstrip('"')
            val = val.lstrip("'").rstrip("'")

        fontKind = self.fontSettingNameToFontKind(name)
        if fontKind:
            d[fontKind] = name,val # Used only by doFont.
#@-node:ekr.20041213082558.2:parseFontLine
#@-node:ekr.20041213082558.1:parseFont & helper
#@+node:ekr.20041119205148:parseHeadline
def parseHeadline (self,s):

    """Parse a headline of the form @kind:name=val
    Return (kind,name,val)."""

    kind = name = val = None

    if g.match(s,0,'@'):
        i = g.skip_id(s,1,chars='-')
        kind = s[1:i].strip()
        if kind:
            # name is everything up to '='
            j = s.find('=',i)
            if j == -1:
                name = s[i:].strip()
            else:
                name = s[i:j].strip()
                # val is everything after the '='
                val = s[j+1:].strip()

    # g.trace("%50s %10s %s" %(name,kind,val))
    return kind,name,val
#@-node:ekr.20041119205148:parseHeadline
#@+node:ekr.20070411101643.2:parseOpenWith & helper
def parseOpenWith (self,p):

    d = {'command': None,}

    s = p.bodyString()
    lines = g.splitLines(s)

    for line in lines:
        self.parseOpenWithLine(line,d)

    return d
#@+node:ekr.20070411101643.4:parseOpenWithLine
def parseOpenWithLine (self,line,d):

    s = line.strip()
    if not s: return

    try:
        s = str(s)
    except UnicodeError:
        pass

    if not g.match(s,0,'#'):
        d['command'] = s
#@-node:ekr.20070411101643.4:parseOpenWithLine
#@-node:ekr.20070411101643.2:parseOpenWith & helper
#@+node:ekr.20041120112043:parseShortcutLine (g.app.config)
def parseShortcutLine (self,s):

    '''Parse a shortcut line.  Valid forms:

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

    name = val = nextMode = None ; nextMode = 'none'
    i = g.skip_ws(s,0)

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

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

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

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

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

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

    # g.trace(pane,name,val,s)
    return name,g.bunch(nextMode=nextMode,pane=pane,val=val)
#@-node:ekr.20041120112043:parseShortcutLine (g.app.config)
#@+node:ekr.20060608222828:parseAbbrevLine (g.app.config)
def parseAbbrevLine (self,s):

    '''Parse an abbreviation line:
    command-name = abbreviation
    return (command-name,abbreviation)
    '''

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

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

    i = g.skip_ws(s,i+1)
    val = s[i:].strip()
    # Ignore comments after the shortcut.
    i = val.find('#')
    if i > -1: val = val[:i].strip()

    if val: return name,val
    else:   return None,None
#@-node:ekr.20060608222828:parseAbbrevLine (g.app.config)
#@-node:ekr.20041213082558:parsers
#@+node:ekr.20041118053731:Getters (c.configSettings)
def get (self,setting,theType):
    '''A helper function: return the commander's setting, checking the type.'''
    return g.app.config.get(self.c,setting,theType)

def getAbbrevDict (self):
    '''return the commander's abbreviation dictionary.'''
    return g.app.config.getAbbrevDict(self.c)

def getBool (self,setting,default=None):
    '''Return the value of @bool setting, or the default if the setting is not found.'''
    return g.app.config.getBool(self.c,setting,default=default)

def getButtons (self):
    '''Return a list of tuples (x,y) for common @button nodes.'''
    return g.app.config.atCommonButtonsList # unusual.

def getColor (self,setting):
    '''Return the value of @color setting.'''
    return g.app.config.getColor(self.c,setting)

def getCommands (self):
    '''Return the list of tuples (headline,script) for common @command nodes.'''
    return g.app.config.atCommonCommandsList # unusual.

def getData (self,setting):
    '''Return a list of non-comment strings in the body text of @data setting.'''
    return g.app.config.getData(self.c,setting)

def getDirectory (self,setting):
    '''Return the value of @directory setting, or None if the directory does not exist.'''
    return g.app.config.getDirectory(self.c,setting)

def getFloat (self,setting):
    '''Return the value of @float setting.'''
    return g.app.config.getFloat(self.c,setting)

def getFontFromParams (self,family,size,slant,weight,defaultSize=12):

    '''Compute a font from font parameters.

    Arguments are the names of settings to be use.
    Default to size=12, slant="roman", weight="normal".

    Return None if there is no family setting so we can use system default fonts.'''

    return g.app.config.getFontFromParams(self.c,
        family, size, slant, weight, defaultSize = defaultSize)

# def getFontDict (self,setting):
    # '''Return the value of @font setting.'''
    # return g.app.config.getFontDict(self.c,setting)

def getInt (self,setting):
    '''Return the value of @int setting.'''
    return g.app.config.getInt(self.c,setting)

def getLanguage (self,setting):
    '''Return the value of @string setting.

    The value of this setting should be a language known to Leo.'''
    return g.app.config.getLanguage(self.c,setting)

def getMenusList (self):
    '''Return the list of entries for the @menus tree.'''
    return g.app.config.menusList # unusual.

def getOpenWith (self):
    '''Return a list of dictionaries corresponding to @openwith nodes.'''
    return g.app.config.getOpenWith(self.c)

def getRatio (self,setting):
    '''Return the value of @float setting.
    Warn if the value is less than 0.0 or greater than 1.0.'''
    return g.app.config.getRatio(self.c,setting)

def getRecentFiles (self):
    '''Return the list of recently opened files.'''
    return g.app.config.getRecentFiles()

def getShortcut (self,shortcutName):
    '''Return the tuple (rawKey,accel) for shortcutName in @shortcuts tree.'''
    return g.app.config.getShortcut(self.c,shortcutName)

def getString (self,setting):
    '''Return the value of @string setting.'''
    return g.app.config.getString(self.c,setting)
#@-node:ekr.20041118053731:Getters (c.configSettings)
#@+node:ekr.20041117081009:Getters... (g.app.config)
#@+node:ekr.20041123070429:canonicalizeSettingName (munge)
def canonicalizeSettingName (self,name):

    if name is None:
        return None

    name = name.lower()
    for ch in ('-','_',' ','\n'):
        name = name.replace(ch,'')

    return g.choose(name,name,None)

munge = canonicalizeSettingName
#@-node:ekr.20041123070429:canonicalizeSettingName (munge)
#@+node:ekr.20041123092357:config.findSettingsPosition
def findSettingsPosition (self,c,setting):

    """Return the position for the setting in the @settings tree for c."""

    munge = self.munge

    root = self.settingsRoot(c)
    if not root:
        return c.nullPosition()

    setting = munge(setting)

    for p in root.subtree_iter():
        h = munge(p.headString())
        if h == setting:
            return p.copy()

    return c.nullPosition()
#@-node:ekr.20041123092357:config.findSettingsPosition
#@+node:ekr.20041117083141:get & allies (g.app.config)
def get (self,c,setting,kind):

    """Get the setting and make sure its type matches the expected type."""

    if c:
        d = self.localOptionsDict.get(c.hash())
        if d:
            val,junk = self.getValFromDict(d,setting,kind)
            if val is not None:
                # if setting == 'targetlanguage':
                    # g.trace(c.shortFileName(),setting,val,g.callers())
                return val

    for d in self.localOptionsList:
        val,junk = self.getValFromDict(d,setting,kind)
        if val is not None:
            kind = d.get('_hash','<no hash>')
            # if setting == 'targetlanguage':
                # g.trace(kind,setting,val,g.callers())
            return val

    for d in self.dictList:
        val,junk = self.getValFromDict(d,setting,kind)
        if val is not None:
            kind = d.get('_hash','<no hash>')
            # if setting == 'targetlanguage':
                # g.trace(kind,setting,val,g.callers())
            return val

    return None
#@+node:ekr.20041121143823:getValFromDict
def getValFromDict (self,d,setting,requestedType,warn=True):

    '''Look up the setting in d. If warn is True, warn if the requested type
    does not (loosely) match the actual type.
    returns (val,exists)'''

    bunch = d.get(self.munge(setting))
    if not bunch: return None,False

    # g.trace(setting,requestedType,bunch.toString())
    val = bunch.val
    if not self.typesMatch(bunch.kind,requestedType):
        # New in 4.4: make sure the types match.
        # A serious warning: one setting may have destroyed another!
        # Important: this is not a complete test of conflicting settings:
        # The warning is given only if the code tries to access the setting.
        if warn:
            g.es_print('warning: ignoring',bunch.kind,'',setting,'is not',requestedType,color='red')
            g.es_print('there may be conflicting settings!',color='red')
        return None, False
    elif val in (u'None',u'none','None','none','',None):
        return None, True # Exists, but is None
    else:
        # g.trace(setting,val)
        return val, True
#@-node:ekr.20041121143823:getValFromDict
#@+node:ekr.20051015093141:typesMatch
def typesMatch (self,type1,type2):

    '''
    Return True if type1, the actual type, matches type2, the requeseted type.

    The following equivalences are allowed:

    - None matches anything.
    - An actual type of string or strings matches anything.
    - Shortcut matches shortcuts.
    '''

    shortcuts = ('shortcut','shortcuts',)

    return (
        type1 == None or type2 == None or
        type1.startswith('string') or
        type1 == 'int' and type2 == 'size' or
        (type1 in shortcuts and type2 in shortcuts) or
        type1 == type2
    )
#@-node:ekr.20051015093141:typesMatch
#@-node:ekr.20041117083141:get & allies (g.app.config)
#@+node:ekr.20051011105014:exists (g.app.config)
def exists (self,c,setting,kind):

    '''Return true if a setting of the given kind exists, even if it is None.'''

    if c:
        d = self.localOptionsDict.get(c.hash())
        if d:
            junk,found = self.getValFromDict(d,setting,kind)
            if found: return True

    for d in self.localOptionsList:
        junk,found = self.getValFromDict(d,setting,kind)
        if found: return True

    for d in self.dictList:
        junk,found = self.getValFromDict(d,setting,kind)
        if found: return True

    # g.trace('does not exist',setting,kind)
    return False
#@-node:ekr.20051011105014:exists (g.app.config)
#@+node:ekr.20060608224112:getAbbrevDict
def getAbbrevDict (self,c):

    """Search all dictionaries for the setting & check it's type"""

    d = self.get(c,'abbrev','abbrev')
    return d or {}
#@-node:ekr.20060608224112:getAbbrevDict
#@+node:ekr.20041117081009.3:getBool
def getBool (self,c,setting,default=None):

    '''Return the value of @bool setting, or the default if the setting is not found.'''

    val = self.get(c,setting,"bool")

    if val in (True,False):
        return val
    else:
        return default
#@-node:ekr.20041117081009.3:getBool
#@+node:ekr.20070926082018:getButtons
def getButtons (self):

    '''Return a list of tuples (x,y) for common @button nodes.'''

    return g.app.config.atCommonButtonsList
#@-node:ekr.20070926082018:getButtons
#@+node:ekr.20080312071248.7:getCommonCommands
def getCommonAtCommands (self):

    '''Return the list of tuples (headline,script) for common @command nodes.'''

    return g.app.config.atCommonCommandsList
#@-node:ekr.20080312071248.7:getCommonCommands
#@+node:ekr.20041122070339:getColor
def getColor (self,c,setting):

    '''Return the value of @color setting.'''

    return self.get(c,setting,"color")
#@-node:ekr.20041122070339:getColor
#@+node:ekr.20071214140900.1:getData
def getData (self,c,setting):

    '''Return a list of non-comment strings in the body text of @data setting.'''

    return self.get(c,setting,"data")
#@-node:ekr.20071214140900.1:getData
#@+node:ekr.20041117093009.1:getDirectory
def getDirectory (self,c,setting):

    '''Return the value of @directory setting, or None if the directory does not exist.'''

    theDir = self.getString(c,setting)

    if g.os_path_exists(theDir) and g.os_path_isdir(theDir):
         return theDir
    else:
        return None
#@-node:ekr.20041117093009.1:getDirectory
#@+node:ekr.20070224075914.1:getEnabledPlugins
def getEnabledPlugins (self):

    '''Return the body text of the @enabled-plugins node.'''

    return g.app.config.enabledPluginsString
#@-node:ekr.20070224075914.1:getEnabledPlugins
#@+node:ekr.20041117082135:getFloat
def getFloat (self,c,setting):

    '''Return the value of @float setting.'''

    val = self.get(c,setting,"float")
    try:
        val = float(val)
        return val
    except TypeError:
        return None
#@-node:ekr.20041117082135:getFloat
#@+node:ekr.20041117062717.13:getFontFromParams (config)
def getFontFromParams(self,c,family,size,slant,weight,defaultSize=12):

    """Compute a font from font parameters.

    Arguments are the names of settings to be use.
    Default to size=12, slant="roman", weight="normal".

    Return None if there is no family setting so we can use system default fonts."""

    family = self.get(c,family,"family")
    if family in (None,""):
        family = self.defaultFontFamily

    size = self.get(c,size,"size")
    if size in (None,0): size = defaultSize

    slant = self.get(c,slant,"slant")
    if slant in (None,""): slant = "roman"

    weight = self.get(c,weight,"weight")
    if weight in (None,""): weight = "normal"

    # g.trace(g.callers(3),family,size,slant,weight,g.shortFileName(c.mFileName))

    return g.app.gui.getFontFromParams(family,size,slant,weight)
#@-node:ekr.20041117062717.13:getFontFromParams (config)
#@+node:ekr.20041117081513:getInt
def getInt (self,c,setting):

    '''Return the value of @int setting.'''

    val = self.get(c,setting,"int")
    try:
        val = int(val)
        return val
    except TypeError:
        return None
#@-node:ekr.20041117081513:getInt
#@+node:ekr.20041117093009.2:getLanguage
def getLanguage (self,c,setting):

    '''Return the setting whose value should be a language known to Leo.'''

    language = self.getString(c,setting)
    # g.trace(setting,language)

    return language
#@-node:ekr.20041117093009.2:getLanguage
#@+node:ekr.20070926070412:getMenusList
def getMenusList (self):

    '''Return the list of entries for the @menus tree.'''

    return g.app.config.menusList
#@-node:ekr.20070926070412:getMenusList
#@+node:ekr.20070411101643:getOpenWith
def getOpenWith (self,c):

    '''Return a list of dictionaries corresponding to @openwith nodes.'''

    val = self.get(c,'openwithtable','openwithtable')

    return val
#@-node:ekr.20070411101643:getOpenWith
#@+node:ekr.20041122070752:getRatio
def getRatio (self,c,setting):

    '''Return the value of @float setting.

    Warn if the value is less than 0.0 or greater than 1.0.'''

    val = self.get(c,setting,"ratio")
    try:
        val = float(val)
        if 0.0 <= val <= 1.0:
            return val
        else:
            return None
    except TypeError:
        return None
#@-node:ekr.20041122070752:getRatio
#@+node:ekr.20041117062717.11:getRecentFiles
def getRecentFiles (self):

    '''Return the list of recently opened files.'''

    return self.recentFiles
#@-node:ekr.20041117062717.11:getRecentFiles
#@+node:ekr.20041117062717.14:getShortcut (config)
def getShortcut (self,c,shortcutName):

    '''Return rawKey,accel for shortcutName'''

    key = c.frame.menu.canonicalizeMenuName(shortcutName)
    key = key.replace('&','') # Allow '&' in names.

    bunchList = self.get(c,key,"shortcut")
    if bunchList:
        bunchList = [bunch for bunch in bunchList
            if bunch.val and bunch.val.lower() != 'none']
        return key,bunchList
    else:
        return key,[]
#@-node:ekr.20041117062717.14:getShortcut (config)
#@+node:ekr.20041117081009.4:getString
def getString (self,c,setting):

    '''Return the value of @string setting.'''

    return self.get(c,setting,"string")
#@-node:ekr.20041117081009.4:getString
#@+node:ekr.20041120074536:settingsRoot
def settingsRoot (self,c):

    '''Return the position of the @settings tree.'''

    # g.trace(c,c.rootPosition())

    for p in c.allNodes_iter():
        if p.headString().rstrip() == "@settings":
            return p.copy()
    else:
        return c.nullPosition()
#@-node:ekr.20041120074536:settingsRoot
#@-node:ekr.20041117081009:Getters... (g.app.config)
#@-node:ekr.20080327061021.230:Improved scripting docs & docstrings
#@-node:ekr.20080315115427.576:Features
#@-node:ekr.20080315083057.10:b3
#@+node:ekr.20080403065258.11:rc1
#@+node:ekr.20080403065258.2:Fixed delete-editor crash
@nocolor

If you do the following sequence of commands

add-editor
add-editor
delete-editor
delete-editor
add-editor

You get an error message. If you continue to use leo the title bar at the top of
the editor comes on permanantly even if there is only one editor, so this could
be a crash rather than a spurious error.

exception executing command
Traceback (most recent call last):

  File "c:\leo.repo\leo-editor\trunk\leo\src\leoCommands.py", line 275, in doCommand
    val = command(event)

  File "c:\leo.repo\leo-editor\trunk\leo\src\leoFrame.py", line 802, in addEditor
    self.updateInjectedIvars(w_old,p)

  File "c:\leo.repo\leo-editor\trunk\leo\src\leoFrame.py", line 1163, in updateInjectedIvars
    w.leo_chapter = cc.getSelectedChapter()

AttributeError: 'NoneType' object has no attribute 'leo_chapter'

EKR: The fix was a bad assumption in addEditor: the name of the last editor need not be '1'



@color
#@nonl
#@+node:ekr.20070318122708:cc.getSelectedChapter
def getSelectedChapter (self):

    cc = self

    return cc.selectedChapter
#@-node:ekr.20070318122708:cc.getSelectedChapter
#@+node:ekr.20060528100747.1:addEditor
def addEditor (self,event=None):

    '''Add another editor to the body pane.'''

    c = self.c ; p = c.currentPosition()

    self.totalNumberOfEditors += 1
    self.numberOfEditors += 1

    if self.numberOfEditors == 2:
        # Inject the ivars into the first editor.
        # Bug fix: Leo 4.4.8 rc1: The name of the last editor need not be '1'
        d = self.editorWidgets ; keys = d.keys()
        if len(keys) == 1:
            w_old = d.get(keys[0])
            self.updateInjectedIvars(w_old,p)
            self.selectLabel(w_old) # Immediately create the label in the old editor.
        else:
            g.trace('can not happen: unexpected editorWidgets',d)

    name = '%d' % self.totalNumberOfEditors
    pane = self.pb.add(name)
    panes = self.pb.panes()
    minSize = float(1.0/float(len(panes)))

    f = self.createEditorFrame(pane)
    << create text widget w >>
    self.editorWidgets[name] = w

    for pane in panes:
        self.pb.configurepane(pane,size=minSize)

    self.pb.updatelayout()
    c.frame.body.bodyCtrl = w

    self.updateInjectedIvars(w,p)
    self.selectLabel(w)
    self.selectEditor(w)
    self.updateEditors()
    c.bodyWantsFocusNow()
#@+node:ekr.20060528110922:<< create text widget w >>
w = self.createTextWidget(f,name=name,p=p)
w.delete(0,'end')
w.insert('end',p.bodyString())
w.see(0)

self.setFontFromConfig(w=w)
self.setColorFromConfig(w=w)
self.createBindings(w=w)
c.k.completeAllBindingsForWidget(w)

self.recolorWidget(p,w)
#@nonl
#@-node:ekr.20060528110922:<< create text widget w >>
#@-node:ekr.20060528100747.1:addEditor
#@-node:ekr.20080403065258.2:Fixed delete-editor crash
#@-node:ekr.20080403065258.11:rc1
#@+node:ekr.20080404074921.4:final
#@+node:ekr.20080404074921.1:Open leoPluginsRef.leo in help menu if leoPlugins.leo does not exist
#@+node:ekr.20050130152008:leoPlugins
def openLeoPlugins (self,event=None):

    '''Open leoPlugins.leo in a new Leo window.'''

    names =  ('leoPlugins.leo','leoPluginsRef.leo')

    c = self ; name = "leoPlugins.leo"

    for name in names:
        fileName = g.os_path_join(g.app.loadDir,"..","plugins",name)
        ok,frame = g.openWithFileName(fileName,c)
        if ok: return

    g.es('not found:', ', '.join(names))
#@-node:ekr.20050130152008:leoPlugins
#@-node:ekr.20080404074921.1:Open leoPluginsRef.leo in help menu if leoPlugins.leo does not exist
#@+node:ekr.20080315115427.2:Fixed big performance bug in find/spell commands.
@nocolor

Fixed huge performance bug.

@color
#@nonl
#@+node:ekr.20031218072017.3091:showSuccess
def showSuccess(self,pos,newpos):

    """Displays the final result.

    Returns self.dummy_vnode, c.edit_widget(p) or c.frame.body.bodyCtrl with
    "insert" and "sel" points set properly."""

    c = self.c ; p = self.p
    sparseFind = c.config.getBool('collapse_nodes_during_finds')
    c.frame.bringToFront() # Needed on the Mac
    redraw = not p.isVisible(c)
    c.beginUpdate()
    try:
        if sparseFind and not c.currentPosition().isAncestorOf(p):
            # New in Leo 4.4.2: show only the 'sparse' tree when redrawing.
            for p2 in c.currentPosition().self_and_parents_iter():
                    p2.contract()
                    redraw = True
        for p in self.p.parents_iter():
            if not p.isExpanded():
                p.expand()
                redraw = True
        p = self.p
        if not p: g.trace('can not happen: self.p is None')
        c.selectPosition(p)
    finally:
        c.endUpdate(redraw)
    if self.in_headline:
        c.editPosition(p)
    # Set the focus and selection after the redraw.
    w = g.choose(self.in_headline,c.edit_widget(p),c.frame.body.bodyCtrl)
    c.widgetWantsFocusNow(w)
    # New in 4.4a3: a much better way to ensure progress in backward searches.
    insert = g.choose(self.reverse,min(pos,newpos),max(pos,newpos))
    #g.trace('reverse,pos,newpos,insert',self.reverse,pos,newpos,insert)
    w.setSelectionRange(pos,newpos,insert=insert)
    w.seeInsertPoint()
    if self.wrap and not self.wrapPosition:
        self.wrapPosition = self.p
#@nonl
#@-node:ekr.20031218072017.3091:showSuccess
#@+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)
        ### w.update() ###
    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."""

    c = self.c ; p = c.currentPosition()
    w = c.frame.body.bodyCtrl
    aspell = self.aspell ; alts = None ; word = None
    sparseFind = c.config.getBool('collapse_nodes_while_spelling')
    trace = False
    try:
        while 1:
            i,j,p,word = self.findNextWord(p)
            # g.trace(i,j,p and p.headString() or '<no p>')
            if not p or not word:
                alts = None
                break
            << Skip word if ignored or in local dictionary >>
            alts = aspell.processWord(word)
            if trace: g.trace('alts',alts and len(alts) or 0,i,j,word,p and p.headString() or 'None')
            if alts:
                c.beginUpdate()
                try:
                    redraw = not p.isVisible(c)
                    # New in Leo 4.4.8: show only the 'sparse' tree when redrawing.
                    if sparseFind and not c.currentPosition().isAncestorOf(p):
                        for p2 in c.currentPosition().self_and_parents_iter():
                            p2.contract()
                            redraw = True
                    for p2 in p.parents_iter():
                        if not p2.isExpanded():
                            p2.expand()
                            redraw = True
                    # c.frame.tree.expandAllAncestors(p)
                    c.selectPosition(p)
                finally:
                    c.endUpdate(redraw)
                    w.setSelectionRange(i,j,insert=j)
                break
    except Exception:
        g.es_exception()
    return alts, word
#@+node:ekr.20051025071455.46:<< Skip word if ignored or in local dictionary >>
@ 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
@c

if self.dictionary.has_key(word.lower()):
    continue
#@-node:ekr.20051025071455.46:<< Skip word if ignored or in local dictionary >>
#@-node:ekr.20051025071455.45:findNextMisspelledWord
#@+node:ekr.20051025071455.47:findNextWord (tkSpell)
def findNextWord(self,p):
    """Scan for the next word, leaving the result in the work widget"""

    c = self.c ; p = p.copy() ; trace = False
    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.headString(),'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.headString())
            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.bodyString())
            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.headString())

    return None,None,None,None
#@nonl
#@-node:ekr.20051025071455.47:findNextWord (tkSpell)
#@-node:ekr.20051025071455.40:find & helpers
#@+node:ekr.20051025071455.22:class tkSpellTab
class tkSpellTab:

    @others
#@+node:ekr.20070212132230.1:tkSpellTab.__init__
def __init__ (self,c,handler,tabName):

    self.c = c
    self.handler = handler
    self.tabName = tabName
    self.change_i, change_j = None,None
    self.createFrame()
    self.createBindings()
    self.fillbox([])
#@-node:ekr.20070212132230.1:tkSpellTab.__init__
#@+node:ekr.20051025120920:createBindings
def createBindings (self):

    c = self.c ; k = c.k
    widgets = (self.listBox, self.outerFrame)

    for w in widgets:

        # Bind shortcuts for the following commands...
        for commandName,func in (
            ('full-command',            k.fullCommand),
            ('hide-spell-tab',          self.handler.hide),
            ('spell-add',               self.handler.add),
            ('spell-find',              self.handler.find),
            ('spell-ignore',            self.handler.ignore),
            ('spell-change-then-find',  self.handler.changeThenFind),
        ):
            junk, bunchList = c.config.getShortcut(commandName)
            for bunch in bunchList:
                accel = bunch.val
                shortcut = k.shortcutFromSetting(accel)
                if shortcut:
                    # g.trace(shortcut,commandName)
                    w.bind(shortcut,func)

    for binding,func in (
        ("<Double-1>",  self.onChangeThenFindButton),
        ("<Button-1>",  self.onSelectListBox),
        ("<Map>",       self.onMap),
        # These never get called because focus is always in the body pane!
        # ("<Up>",        self.up),
        # ("<Down>",      self.down),
    ):
        self.listBox.bind(binding,func)
#@-node:ekr.20051025120920:createBindings
#@+node:ekr.20070212132230.2:createFrame
def createFrame (self):

    c = self.c ; log = c.frame.log ; tabName = self.tabName
    setFont = False

    parentFrame = log.frameDict.get(tabName)
    w = log.textDict.get(tabName)
    w.pack_forget()

    # Set the common background color.
    bg = c.config.getColor('log_pane_Spell_tab_background_color') or 'LightSteelBlue2'

    if setFont:
        fontSize = g.choose(sys.platform.startswith('win'),9,14)

    << Create the outer frames >>
    << Create the text and suggestion panes >>
    << Create the spelling buttons >>

    # Pack last so buttons don't get squished.
    self.outerScrolledFrame.pack(expand=1,fill='both',padx=2,pady=2)
#@+node:ekr.20051113090322:<< Create the outer frames >>
self.outerScrolledFrame = Pmw.ScrolledFrame(
    parentFrame,usehullsize = 1)

self.outerFrame = outer = self.outerScrolledFrame.component('frame')
self.outerFrame.configure(background=bg)

for z in ('borderframe','clipper','frame','hull'):
    self.outerScrolledFrame.component(z).configure(
        relief='flat',background=bg)
#@-node:ekr.20051113090322:<< Create the outer frames >>
#@+node:ekr.20051025071455.23:<< Create the text and suggestion panes >>
f2 = Tk.Frame(outer,bg=bg)
f2.pack(side='top',expand=0,fill='x')

self.wordLabel = Tk.Label(f2,text="Suggestions for:")
self.wordLabel.pack(side='left')

if setFont:
    self.wordLabel.configure(font=('verdana',fontSize,'bold'))

fpane = Tk.Frame(outer,bg=bg,bd=2)
fpane.pack(side='top',expand=1,fill='both')

self.listBox = Tk.Listbox(fpane,height=6,width=10,selectmode="single")
self.listBox.pack(side='left',expand=1,fill='both')
if setFont:
    self.listBox.configure(font=('verdana',fontSize,'normal'))

listBoxBar = Tk.Scrollbar(fpane,name='listBoxBar')

bar, txt = listBoxBar, self.listBox
txt ['yscrollcommand'] = bar.set
bar ['command'] = txt.yview
bar.pack(side='right',fill='y')
#@-node:ekr.20051025071455.23:<< Create the text and suggestion panes >>
#@+node:ekr.20051025071455.24:<< Create the spelling buttons >>
# Create the alignment panes
buttons1 = Tk.Frame(outer,bd=1,bg=bg)
buttons2 = Tk.Frame(outer,bd=1,bg=bg)
buttons3 = Tk.Frame(outer,bd=1,bg=bg)
for w in (buttons1,buttons2,buttons3):
    w.pack(side='top',expand=0,fill='x')

buttonList = []
if setFont:
    font = ('verdana',fontSize,'normal')
width = 12
for frame, text, command in (
    (buttons1,"Find",self.onFindButton),
    (buttons1,"Add",self.onAddButton),
    (buttons2,"Change",self.onChangeButton),
    (buttons2,"Change, Find",self.onChangeThenFindButton),
    (buttons3,"Ignore",self.onIgnoreButton),
    (buttons3,"Hide",self.onHideButton),
):
    if setFont:
        b = Tk.Button(frame,font=font,width=width,text=text,command=command)
    else:
        b = Tk.Button(frame,width=width,text=text,command=command)
    b.pack(side='left',expand=0,fill='none')
    buttonList.append(b)

# Used to enable or disable buttons.
(self.findButton,self.addButton,
 self.changeButton, self.changeFindButton,
 self.ignoreButton, self.hideButton) = buttonList
#@-node:ekr.20051025071455.24:<< Create the spelling buttons >>
#@-node:ekr.20070212132230.2:createFrame
#@+node:ekr.20051025071455.29:Event handlers
#@+node:ekr.20051025071455.30:onAddButton
def onAddButton(self):
    """Handle a click in the Add button in the Check Spelling dialog."""

    self.handler.add()
    self.change_i, self.change_j = None,None
#@-node:ekr.20051025071455.30:onAddButton
#@+node:ekr.20051025071455.32:onChangeButton & onChangeThenFindButton
def onChangeButton(self,event=None):

    """Handle a click in the Change button in the Spell tab."""

    self.handler.change()
    self.updateButtons()
    self.change_i, self.change_j = None,None


def onChangeThenFindButton(self,event=None):

    """Handle a click in the "Change, Find" button in the Spell tab."""

    if self.handler.change():
        self.handler.find()
    self.updateButtons()
    self.change_i, self.change_j = None,None
#@-node:ekr.20051025071455.32:onChangeButton & onChangeThenFindButton
#@+node:ekr.20051025071455.33:onFindButton
def onFindButton(self):

    """Handle a click in the Find button in the Spell tab."""

    c = self.c
    self.handler.find()
    self.updateButtons()
    c.invalidateFocus()
    c.bodyWantsFocusNow()
    self.change_i, self.change_j = None,None
#@-node:ekr.20051025071455.33:onFindButton
#@+node:ekr.20051025071455.34:onHideButton
def onHideButton(self):

    """Handle a click in the Hide button in the Spell tab."""

    self.handler.hide()
    self.change_i, self.change_j = None,None
#@-node:ekr.20051025071455.34:onHideButton
#@+node:ekr.20051025071455.31:onIgnoreButton
def onIgnoreButton(self,event=None):

    """Handle a click in the Ignore button in the Check Spelling dialog."""

    self.handler.ignore()
    self.change_i, self.change_j = None,None
#@nonl
#@-node:ekr.20051025071455.31:onIgnoreButton
#@+node:ekr.20051025071455.49:onMap
def onMap (self, event=None):
    """Respond to a Tk <Map> event."""

    # self.update(show= False, fill= False)
    self.updateButtons()
#@-node:ekr.20051025071455.49:onMap
#@+node:ekr.20051025071455.50:onSelectListBox
def onSelectListBox(self, event=None):
    """Respond to a click in the selection listBox."""

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

    if self.change_i is None:
        # A bad hack to get around the fact that only one selection
        # exists at any one time on Linux.
        i,j = w.getSelectionRange()
        # g.trace('setting',i,j)
        self.change_i,self.change_j = i,j

    self.updateButtons()

    return 'continue'
#@-node:ekr.20051025071455.50:onSelectListBox
#@+node:ekr.20080404095546.1:down/up
def down (self,event):

    # Work around an old Python bug.  Convert strings to ints.
    w = self.listBox ; items = w.curselection()
    try: items = map(int, items)
    except ValueError: pass

    if items:
        n = items[0]
        if n + 1 < len(self.positionList):
            w.selection_clear(n)
            w.selection_set(n+1)
    else:
        w.selection_set(0)
    w.focus_force()
    return 'break'


def up (self,event):

    # Work around an old Python bug.  Convert strings to ints.
    w = self.listBox ; items = w.curselection()
    try: items = map(int, items)
    except ValueError: pass

    if items: n = items[0]
    else:     n = 0
    w.selection_clear(n)
    w.selection_set(max(0,n-1))
    w.focus_force()
    return 'break'
#@-node:ekr.20080404095546.1:down/up
#@-node:ekr.20051025071455.29:Event handlers
#@+node:ekr.20051025071455.42:Helpers
#@+node:ekr.20051025071455.43:bringToFront
def bringToFront (self):

    # g.trace('tkSpellTab',g.callers())
    self.c.frame.log.selectTab('Spell')
#@-node:ekr.20051025071455.43:bringToFront
#@+node:ekr.20051025071455.44:fillbox
def fillbox(self, alts, word=None):
    """Update the suggestions listBox in the Check Spelling dialog."""

    self.suggestions = alts

    if not word:
        word = ""

    self.wordLabel.configure(text= "Suggestions for: " + word)
    self.listBox.delete(0, "end")

    for i in xrange(len(self.suggestions)):
        self.listBox.insert(i, self.suggestions[i])

    # This doesn't show up because we don't have focus.
    if len(self.suggestions):
        self.listBox.select_set(1)
#@-node:ekr.20051025071455.44:fillbox
#@+node:ekr.20051025071455.48:getSuggestion
def getSuggestion(self):
    """Return the selected suggestion from the listBox."""

    # Work around an old Python bug.  Convert strings to ints.
    items = self.listBox.curselection()
    try:
        items = map(int, items)
    except ValueError: pass

    if items:
        n = items[0]
        suggestion = self.suggestions[n]
        return suggestion
    else:
        return None
#@-node:ekr.20051025071455.48:getSuggestion
#@+node:ekr.20051025071455.51:update (no longer used)
# def update(self,show=True,fill=False):

    # """Update the Spell Check dialog."""

    # c = self.c

    # if fill:
        # self.fillbox([])

    # self.updateButtons()

    # if show:
        # self.bringToFront()
        # c.bodyWantsFocus()
#@-node:ekr.20051025071455.51:update (no longer used)
#@+node:ekr.20051025071455.52:updateButtons (spellTab)
def updateButtons (self):

    """Enable or disable buttons in the Check Spelling dialog."""

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

    start, end = w.getSelectionRange()
    state = g.choose(self.suggestions and start,"normal","disabled")

    self.changeButton.configure(state=state)
    self.changeFindButton.configure(state=state)

    # state = g.choose(self.c.undoer.canRedo(),"normal","disabled")
    # self.redoButton.configure(state=state)
    # state = g.choose(self.c.undoer.canUndo(),"normal","disabled")
    # self.undoButton.configure(state=state)

    self.addButton.configure(state='normal')
    self.ignoreButton.configure(state='normal')
#@-node:ekr.20051025071455.52:updateButtons (spellTab)
#@-node:ekr.20051025071455.42:Helpers
#@-node:ekr.20051025071455.22:class tkSpellTab
#@+node:ekr.20051025071455.50:onSelectListBox
def onSelectListBox(self, event=None):
    """Respond to a click in the selection listBox."""

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

    if self.change_i is None:
        # A bad hack to get around the fact that only one selection
        # exists at any one time on Linux.
        i,j = w.getSelectionRange()
        # g.trace('setting',i,j)
        self.change_i,self.change_j = i,j

    self.updateButtons()

    return 'continue'
#@-node:ekr.20051025071455.50:onSelectListBox
#@+node:ekr.20051025071455.52:updateButtons (spellTab)
def updateButtons (self):

    """Enable or disable buttons in the Check Spelling dialog."""

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

    start, end = w.getSelectionRange()
    state = g.choose(self.suggestions and start,"normal","disabled")

    self.changeButton.configure(state=state)
    self.changeFindButton.configure(state=state)

    # state = g.choose(self.c.undoer.canRedo(),"normal","disabled")
    # self.redoButton.configure(state=state)
    # state = g.choose(self.c.undoer.canUndo(),"normal","disabled")
    # self.undoButton.configure(state=state)

    self.addButton.configure(state='normal')
    self.ignoreButton.configure(state='normal')
#@-node:ekr.20051025071455.52:updateButtons (spellTab)
#@+node:ekr.20080404095546.1:down/up
def down (self,event):

    # Work around an old Python bug.  Convert strings to ints.
    w = self.listBox ; items = w.curselection()
    try: items = map(int, items)
    except ValueError: pass

    if items:
        n = items[0]
        if n + 1 < len(self.positionList):
            w.selection_clear(n)
            w.selection_set(n+1)
    else:
        w.selection_set(0)
    w.focus_force()
    return 'break'


def up (self,event):

    # Work around an old Python bug.  Convert strings to ints.
    w = self.listBox ; items = w.curselection()
    try: items = map(int, items)
    except ValueError: pass

    if items: n = items[0]
    else:     n = 0
    w.selection_clear(n)
    w.selection_set(max(0,n-1))
    w.focus_force()
    return 'break'
#@-node:ekr.20080404095546.1:down/up
#@-node:ekr.20080315115427.2:Fixed big performance bug in find/spell commands.
#@+node:ekr.20080405074410.1:Patched fileActions plugin
@nocolor

The only change I made was to add this code:

   if not p.isAnyAtFileNode():
       return

after this code:

   if not c or not p:
       return

in the method:

   def onIconDoubleClick(tag, keywords):
#@nonl
#@-node:ekr.20080405074410.1:Patched fileActions plugin
#@-node:ekr.20080404074921.4:final
#@+node:ekr.20080405074410.2:Fixed several unit testing bugs relating to doTests(all=True)
@nocolor

I created two buttons: test and run-all-tests. The code is as follows:

=== test ===
import leoTest
leoTest.doTests(c, all=False)
=== end test ===

=== run-all-tests ===
import leoTest
leoTest.doTests(c, all=True)
=== end run-all-tests ===

The "test" button works as expected, but run-all-tests produces
the following output in the Log pane:

exception executing script
AttributeError: 'NoneType' object has no attribute 'v'
--------------------
 line 1270:         return (
* line 1271:             p1.v == p2.v and
 line 1272:             p1.stack == p2.stack and
 line 1273:             p1.childIndex() == p2.childIndex())

@color
#@nonl
#@+node:ekr.20051104075904.4:doTests...
def doTests(c,all,verbosity=1):

    if all:
        p = c.rootPosition()
    else:
        p = c.currentPosition()
    p1 = p.copy()

    try:
        g.unitTesting = g.app.unitTesting = True
        g.app.unitTestDict["fail"] = False
        g.app.unitTestDict['c'] = c
        g.app.unitTestDict['g'] = g
        g.app.unitTestDict['p'] = p and p.copy()

        # c.undoer.clearUndoState() # New in 4.3.1.
        changed = c.isChanged()
        suite = unittest.makeSuite(unittest.TestCase)

        # New in Leo 4.4.8: ignore everything in @ignore trees.
        if all: last = None
        else:   last = p.nodeAfterTree()
        while p and p != last: #  Don't use p.isEqual: it assumes last != None
            h = p.headString()
            if g.match_word(h,0,'@ignore'):
                p.moveToNodeAfterTree()
            elif isTestNode(p): # @test
                test = makeTestCase(c,p)
                if test: suite.addTest(test)
                p.moveToThreadNext()
            elif isSuiteNode(p): # @suite
                # g.trace(p.headString())
                test = makeTestSuite(c,p)
                if test: suite.addTest(test)
                p.moveToThreadNext()
            else:
                p.moveToThreadNext()

        # Verbosity: 1: print just dots.
        unittest.TextTestRunner(verbosity=verbosity).run(suite)
    finally:
        c.setChanged(changed) # Restore changed state.
        c.selectPosition(p1)
        g.unitTesting = g.app.unitTesting = False
#@+node:ekr.20051104075904.5:class generalTestCase
class generalTestCase(unittest.TestCase):

    """Create a unit test from a snippet of code."""

    @others
#@+node:ekr.20051104075904.6:__init__
def __init__ (self,c,p):

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

    self.c = c
    self.p = p.copy()
#@-node:ekr.20051104075904.6:__init__
#@+node:ekr.20051104075904.7: fail
def fail (self,msg=None):

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

    # __pychecker__ = '--no-argsused'
        #  msg needed so signature matches base class.

    import leoGlobals as g

    g.app.unitTestDict["fail"] = g.callers()
#@-node:ekr.20051104075904.7: fail
#@+node:ekr.20051104075904.8:setUp
def setUp (self):

    c = self.c ; p = self.p

    c.selectPosition(p)
#@-node:ekr.20051104075904.8:setUp
#@+node:ekr.20051104075904.9:tearDown
def tearDown (self):

    pass

    # To do: restore the outline.
#@-node:ekr.20051104075904.9:tearDown
#@+node:ekr.20051104075904.10:runTest
def runTest (self,define_g = True):

    c = self.c ; p = self.p.copy()
    script = g.getScript(c,p).strip()
    self.assert_(script)

    # New in Leo 4.4.3: always define the entries in g.app.unitTestDict.
    g.app.unitTestDict = {'c':c,'g':g,'p':p and p.copy()}

    if define_g:
        d = {'c':c,'g':g,'p':p}
    else:
        d = {}

    # Execute the script. Let unit test handle any errors!

    if 0: # debug
        import pdb
        pdb.run(script+'\n',d)
    else:
        exec script + '\n' in d
#@-node:ekr.20051104075904.10:runTest
#@+node:ekr.20051104075904.11:shortDescription
def shortDescription (self):

    return self.p.headString() + '\n'
#@-node:ekr.20051104075904.11:shortDescription
#@-node:ekr.20051104075904.5:class generalTestCase
#@+node:ekr.20051104075904.12:makeTestSuite
@ This code executes the script in an @suite node.  This code assumes:
- The script creates a one or more unit tests.
- The script puts the result in g.app.scriptDict["suite"]
@c

def makeTestSuite (c,p):

    """Create a suite of test cases by executing the script in an @suite node."""

    p = p.copy()

    h = p.headString()
    script = g.getScript(c,p).strip()
    if not script:
        print "no script in %s" % h
        return None

    try:
        exec script + '\n' in {'c':c,'g':g,'p':p}
        suite = g.app.scriptDict.get("suite")
        if not suite:
            print "%s script did not set g.app.scriptDict" % h
        return suite
    except:
        g.trace('Exception creating test cases for %s' % p.headString())
        g.es_exception()
        return None
#@-node:ekr.20051104075904.12:makeTestSuite
#@+node:ekr.20051104075904.13:makeTestCase
def makeTestCase (c,p):

    p = p.copy()

    if p.bodyString().strip():
        return generalTestCase(c,p)
    else:
        return None
#@-node:ekr.20051104075904.13:makeTestCase
#@-node:ekr.20051104075904.4:doTests...
#@+node:ekr.20051104075904.69: makeEditBodySuite
def makeEditBodySuite(c,p):

    """Create an Edit Body test for every descendant of testParentHeadline.."""

    # p = c.currentPosition()
    u = testUtils(c)
    assert c.positionExists(p)
    data_p = u.findNodeInTree(p,"editBodyTests")   
    assert(data_p)
    temp_p = u.findNodeInTree(data_p,"tempNode")
    assert(temp_p)

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

    for p in data_p.children_iter():
        if p.headString()=="tempNode": continue # TempNode now in data tree.
        before = u.findNodeInTree(p,"before")
        after  = u.findNodeInTree(p,"after")
        sel    = u.findNodeInTree(p,"selection")
        ins    = u.findNodeInTree(p,"insert")
        if before and after:
            test = editBodyTestCase(c,p,before,after,sel,ins,temp_p)
            suite.addTest(test)
        else:
            print 'missing "before" or "after" for', p.headString()

    return suite
#@-node:ekr.20051104075904.69: makeEditBodySuite
#@-node:ekr.20080405074410.2:Fixed several unit testing bugs relating to doTests(all=True)
#@-node:ekr.20080227135412:4.4.8
#@-all
#@nonl
#@-node:EKR.20040429143933:@thin leoProjects.txt
#@-leo
