Short summary
About Leo's panes and icons (for newbies)
This node contains text
This headline is marked
This is a cloned headline
This is a cloned headline
This node contains no text
Please use SourceForge to ask questions and make comments
Acknowledgements
What people are saying about Leo
Quote of the Month
From Dan Winkler
Annonymous
Speed Ream's slashdot article (Why Leo is interesting)
Open Software award nominations
Alex Abacus
Steve Allen
Shakeeb Alireza
Bruce M. Bolden
Serge Brisson
Korakot Chaovavanich
F. Geiger
Zak Greant
Lyn Adams Headley
Dave Hein
Travers A. Hough
Bob Hustead
Austin King
Nicola Larosa
Tom Lee
Michael Manti
Marcus A. Martin
David McNab
Joe Orr
Joe Orr 2
Marshall Parsons
Bruce Rafnel
Rich Reis
Steven P. Schaefer
John Sequeira
Brian Takita
Kent Tenney
Jim Vickroy
Dan Winkler 1
Dan Winkler 2
Release Notes
Warnings
Installation notes
System requirements
How to install leo.py on Linux
How to install leo.py on Windows
How to install leo.py on MacOS X Jaguar
From Joe Grace 2/17/03
From Bruce Bolden 1
From Bruce Bolden 2
How to install leo.py on MacOS X Jaguar & Aqua
Installing Leo under Aqua
Making .leo documents open with Leo
How to install the Borland version of Leo on Windows
How to associate leo.py with .leo files on Windows
Putting the Leo icon in Leo windows
Quick start
Latest version
3.11
New in 3.11 final
Bug fixes
Added call to c.redraw() in fileCommands.readAtFileNodes
Added getpreferredencoding to leoGlobals.py
Eliminated caught exception in frame.setTabWidth
Fixed bug in skip_long
Fixed bug that prevented email line in About Leo from working
Fixed unicode bugs when Tangling and Untangling
Fixed various bugs reported by Pychecker
Removed all __del__ methods
New and improved features and commands
Added patch by Davide Salomoni to OnIconDoubleClick
@lineending directive specifies line endings
Added thin_at_file_trees & load_derived_files_immediately options
** Leo now uses unicode internally
Added calls to toEncodedString before all calls to write
Check for valid encoding when setting app().tkEncoding
How Leo handles unicode
Unicode settings
Unicode theory of operation
Use app().tkEncoding when writing to log pane
** Improved hooks & a new plugin architecture
New plugin architecture
@file plugins/mod_open_with.py
on_idle
<< update v's body text >>
<< set s to the new text >>
<< set conflict flag >>
create_open_with_menu
<< create the Open With menu >>
Major reorganization of hooks
About hooks
Added several new plugins
Improved Open With plugin
Added logic to retain log output until a log exists
New configuration options & plugins
Plugins
Unicode settings
Colors for Show Invisibibles command
look_for_control_drag_on_mouse_down option
body_insertion_cursor_color setting
time/date settings
New commands
Toggle Angle Brackets
Insert Body/Headline Time/Date
Go To First/last Node
Go To Parent
Go To Next/Previous Sibling
Write Missing @file Nodes
Improved commands
Added support for body/headline_gmt_time settings
Tangle now supports output_newline option
Major improvements to expand/contract commands
Leo now allows multiple writes of cloned nodes
The Sort Siblings command can now sort top-level nodes
Changed locks on customizeLeo.py
Other improvements
Allow color param when queuing messages to log pane
Improved scrolling when moving right
Put file name first in window title
Leo scrolls partially visible headlines into full view
Allowed @comment with @language plain
The Find button is now default button in the Find panel
Improved syntax coloring for html and xml
example of syntax coloring for html
Improved Prefs panel
Removed setup.py from distributed files
Bug fixes
Eliminated potential hang in Open With command
Fixed 2 line number bugs
Fixed bug involving nested comments in sentinel lines
Fixed bugs in promote/demote
Fixed configuration bug.
Fixed crasher in undo logic
Fixed drag problem
Fixed huge performance bug in v.unjoinNode
Fixed long-standing control-v bug on Linux
Fixed minor import bugs
Fixed problem with block comments
Fixed several problems revealed by French plugin
Fixed several problems with noweb mode
Fixed syntax coloring bugs
Full recolor after paste
Installer now creates plugins directory
Removed duplicate redraws when renaming nodes
Udated recent files menu when saving a new .leo file
Worked around bugs in Python 2.3 a1
leo.py versions
3.11
New in 3.11 final
Bug fixes
Added call to c.redraw() in fileCommands.readAtFileNodes
Added getpreferredencoding to leoGlobals.py
Eliminated caught exception in frame.setTabWidth
Fixed bug in skip_long
Fixed bug that prevented email line in About Leo from working
Fixed unicode bugs when Tangling and Untangling
Fixed various bugs reported by Pychecker
Removed all __del__ methods
New and improved features and commands
Added patch by Davide Salomoni to OnIconDoubleClick
@lineending directive specifies line endings
Added thin_at_file_trees & load_derived_files_immediately options
** Leo now uses unicode internally
Added calls to toEncodedString before all calls to write
Check for valid encoding when setting app().tkEncoding
How Leo handles unicode
Unicode settings
Unicode theory of operation
Use app().tkEncoding when writing to log pane
** Improved hooks & a new plugin architecture
New plugin architecture
@file plugins/mod_open_with.py
on_idle
<< update v's body text >>
<< set s to the new text >>
<< set conflict flag >>
create_open_with_menu
<< create the Open With menu >>
Major reorganization of hooks
About hooks
Added several new plugins
Improved Open With plugin
Added logic to retain log output until a log exists
New configuration options & plugins
Plugins
Unicode settings
Colors for Show Invisibibles command
look_for_control_drag_on_mouse_down option
body_insertion_cursor_color setting
time/date settings
New commands
Toggle Angle Brackets
Insert Body/Headline Time/Date
Go To First/last Node
Go To Parent
Go To Next/Previous Sibling
Write Missing @file Nodes
Improved commands
Added support for body/headline_gmt_time settings
Tangle now supports output_newline option
Major improvements to expand/contract commands
Leo now allows multiple writes of cloned nodes
The Sort Siblings command can now sort top-level nodes
Changed locks on customizeLeo.py
Other improvements
Allow color param when queuing messages to log pane
Improved scrolling when moving right
Put file name first in window title
Leo scrolls partially visible headlines into full view
Allowed @comment with @language plain
The Find button is now default button in the Find panel
Improved syntax coloring for html and xml
example of syntax coloring for html
Improved Prefs panel
Removed setup.py from distributed files
Bug fixes
Eliminated potential hang in Open With command
Fixed 2 line number bugs
Fixed bug involving nested comments in sentinel lines
Fixed bugs in promote/demote
Fixed configuration bug.
Fixed crasher in undo logic
Fixed drag problem
Fixed huge performance bug in v.unjoinNode
Fixed long-standing control-v bug on Linux
Fixed minor import bugs
Fixed problem with block comments
Fixed several problems revealed by French plugin
Fixed several problems with noweb mode
Fixed syntax coloring bugs
Full recolor after paste
Installer now creates plugins directory
Removed duplicate redraws when renaming nodes
Udated recent files menu when saving a new .leo file
Worked around bugs in Python 2.3 a1
3.10
Fixed bugs
Allowed abbreviated xml tags.
Changed shortcut to Find Previous so Alt - F4 closes Leo
Changed url of help file
Fixed @nsf bug reported by Travers A. Hough
Fixed a bug in Untangle
Fixed bug in Python importer
Fixed bug involving redrawing screen initially
Fixed bugs involving @nosentinelsfile
Fixed crashers in colorizeAnyLanguage
Fixed environment bug in Execute Script command
Fixed major memory leak
Fixed path problem in paths passed to tangle_done.py
Fixed problems with Open With code in customizeLeo.py
Fixed Recent Files command
Fixed reversion involving @comment & @language together
Fixed undo bug
Added features
New Mark Clones and Go To Next Clone commands.
New Export Headlines command
Control-dragging a node now clones it
New "Node Only checkbox in the Find/Change panel
@root options
Converted leoConfig.py to use @root-code
Added @file options
Added support for underlined menu items
Improved Leo's window icon
Added easy way of replacing any Leo code
Added convenience routines to redirect stderr and stdout
Added "event1" and "event2" hooks
Added contributed code to support @read-only nodes
Known bugs
Bugs in undoReplace logic
leo.py 3.9
Fixed bugs
Fixed bug involving @language
Correctly colors Pascal's "private" and "uses" keywords
Fixed a major bug in top()
Fixed bugs when importing Java files.
Convert All Blanks/Tabs commands honor @tabwidth directives
New documentation for leo.py 3.9
Major performance improvements
Incremental syntax coloring speeds redraws
Incremental undo reduces memory usages significantly
Major configuration improvements
Chapter 8: Customizing Leo
Using leoConfig.leo and leoConfig.txt
Using plugins and hooks
New plugin architecture
@file plugins/mod_open_with.py
on_idle
<< update v's body text >>
<< set s to the new text >>
<< set conflict flag >>
create_open_with_menu
<< create the Open With menu >>
About hooks
SECURITY WARNINGS
The danger of trusting code in shared .leo files
NEVER use this kind of code in a hook!!
Don't use rexec !!
<< change all instances of rexec to exec in v's body >>
<< delete thisNode >>
<< clear the undo stack >>
Convenience routines for plugins
Convenience functions to enable and disable the "idle" hook
Convenience methods to create, destroy and modify menus
createMenuItemsFromTable
createNewMenu
createOpenWithMenuFromTable
deleteMenu
deleteMenuItem
Example: how to create a menu
Translating menus into other languages
Putting the Leo icon in Leo windows
More flexible creation of non-existent directories
New commands & features
@url
Execute Script command
Go To Line Number command
More flexible creation of non-existent directories
Much better support for cweb
About cweb mode
Open With command
createOpenWithMenuFromTable
Optional Emacs-style auto-indent
Putting the Leo icon in Leo windows
Reformat Paragraph command
Appendix 1 to Chapter 4: Using @rawfile, @silentfile and @nosentinlefile trees
leo.py 3.8
Fixed bugs
Crashers
Fixed another crasher in scanAllDirectives
Fixed assert failure in putBodyPart
Fixed crasher in scanAllDirectives
Bugs involving paths & derived files
Fixed relative path bugs in tangle.scanAllDirectives
Fixed @delims "round trip" bug
Fixed cweb sentinel bug
Fixed import to @root problems
Leo generates @@ sentinels only for real Leo directives
Minor syntax coloring bugs
Colorizer now recognizes comments after preprocessor directives
Syntax color perl's alarm function
Fixed block-comment bug in Java and HTML
Minor user interface bugs
Worked around Control-T bug
Fixed bug involving menu pastes in headlines
fixed Unicode bug in setBodyStringOrPane
New & changed features
New @rawfile & @silentfile trees
Appendix 1 to Chapter 4: Using @rawfile, @silentfile and @nosentinlefile trees
New @raw, @end_raw and @quiet directives
@raw and @end_raw directives delimit raw text
@verbose, @terse, @quiet and @silent directives control comments in @root trees
Much better support for CWEB
About cweb mode
Allow @comment and @language to coexist
Configuration settings and commands
Added stylesheet option to support <?xml-stylesheet...?> line
Added Configuration Commands to help menu
Added output_initial_comment option
Added output_newline option
Changed path_directive_creates_directories to create_nonexistent_directories
Minor changes
Added support for forth language comments
All export commands now use output_newline option
Changed names of commands in help menu and corresponding entries in leoConfig.leo
Created es_exception
Created reloadAll in leoGlobals.py
Forced focus to body pane after ending headline edit
Use // as single-line comment in Java
wrote "finished" after write @file nodes command
Coming soon: Leo 4.0, a major improvement
leo.py 3.7
Fixed Bugs
Language and syntax coloring bugs
Fixed @comment bug
Fixed colorizing for "while" in C
Fixed @language plain # bug
Fixed @language plain single-quote bug
Bugs in Edit body commands
Bugs relating to tangling and derived files
Fixed major bug in @file read code
Fixed minor read bug
Fixed @comment bug
Fixed a bug that affected wrapping in doc parts
Other bugs
Fixed reversion in Tangle/Untangle code
Fixed undo cloned headline bug
Fixed Find previous bug
Fixed tab problems
Leo now remembers insert point always
Removed setup.py from binary distribution
Improvements
Many new user options
General configuration options
path_directive_creates_directories
relative_path_base_directory
remove_sentinels_extension
write_clone_indices
Secondary pane ratios
User-configurable colors
User-configurable shortcuts for commands
Improved support for Unicode
End of cursed newlines
Code is now compatible with Python 2.1
Better file and path handling
Better support for Perl+pod
New End Edit Headline and Abort Edit Headline commands
Allow reads and writes of cloned @file nodes
Leo now closes an empty window when opening a recent file
Improved derived files
Improved enabling of menu items in popup menus
About Leo dialog is now non-modal
leo.py 3.6
Known bug: problems with Python window
Bug fixes
Fixed problems with opening and writing leoConfig.txt on Linux
Fixed problems renaming files on Linux
Fixed missing headline bug on startup
Improved call to os.path.join in leoApp.finishCreate
Improved how directories are handled in @root trees
Dave Hein: fixed Tk 8.4 problem in Prefs Panel
Dave Hein: added CheckVersion to leoUtils.py
Dave Hein: the Python window now works on Linux.
Steven P. Schaefer: Improved handling of directives in @root trees
Steven P. Schaefer: Fixed cursed newline bug for Linux
Improvements to existing features
Leo now remembers scrollbar position and insert point of previously visited nodes
Body pane now gets focus after Go Back/Next
Improved all commands in Help menu
Removed most error messages in atFile::read logic
Added shortcuts for Read/Write @file Nodes commands
Tomaz Ficko: Added support for MouseWheel (Linux only)
New features
Added Match Brackets command
Added PHP support
Added LaTex support
leo.py 3.5
Bug fixes
Called cf.flush in leoConfig.update
Fixed crasher in leoFileCommands.readOutlineOnly
Fixed crasher when leoConfig.txt did not exist
Fixed cursed newline bug in leoAtFile
Rewrote scanAllDirectives routines to handle relative paths
Suppressed erroneous tangle warning about relative paths
Improvements
Added configuration settings for horizontal scrollbars
Added commands to the help menu
Added logic to test for read-only files
Added importFiles script
leo.py 3.4
Configuration settings and leoConfig.leo
Added comments to leoConfig.txt about split direction and ratio
Added save_clears_undo_buffer config option
Corrected spelling of limit_directory_search_extenstion
Created leoConfig.leo
Fixed a major config bug. This fixes font problems
Improved Recent Files menu
Leo honors pane ratio stored in .leo files
Leo writes prefs if leoConfig.txt is read-only
Made leoConfig.txt be read/write by default
Toggle Split Direction now updates leoConfig.txt
Documentation for code in leo.py that may be of wider interest
Code hidden in leo.py
c2py.py
disStats.py
Tkinter utilities
Font dialog
Syntax coloring code
Tree code
Fixed bugs
Fixed another clone bug: created setAncestorsOfClonedNodesInTreeDirty
Fixed bugs that delayed writing of find and prefs panels
Fixed revert problems in font panel
Fixed several unicode bugs
Fixed wandering insertion point bug
Minor improvements
Syntax colorer now honors @comment
Added @ignore to "raw" imported files
Added log message for Remove Sentinels command
Improved highlighting of whitespace
Remove Sentinels command now uses .txt extension instead of .tmp
leo.py 3.3
Added support for Unicode
Improved the Import commands & fixed Javadoc problem
Fixed crasher when Tangling
Fixed problems with negative tab widths
Fixed numerous problems related to configuration settings
leo.py 3.2
Nested @others directives now valid
@file test.txt
class a
a::one
a:two
class b
b::one
b:two
Improved Import command
Reassigned shortcuts in the Outline-Move/Select menu
Fixed crasher in the Preferences Panel
Improved syntax coloring of continued C strings
Made many changes to Color Panel
Added email address & URL to About Leo dialog
added disStats.py
leo.py 3.1
Added compare panel
Added new settings to leoConfig.txt
Changed precedence scheme for preferences
Fixed @tabwidth, @pagewidth and @language directives
Flatten Outline now escapes only the first non-blank character of each line.
Open Python Window command adds path to IDLE automatically
Python 2.2 required dialog tells where to download Python 2.2 and Tk 8.3
leo.py 3.0
Optional new file format
New features (in approximate order of importance)
Added many settings to leoConfig.txt file
New windows now open using config params.
Added autoscrolling in the outline pane
Color panel
Font panel
Leo now opens windows at the position in which they were saved
Added Toggle Split Direction
Improved syntax coloring for @comment directives
Leo warns and aborts if not Python 2.2 or above
All panels now have labeled frames and grooved borders
Leo directives are now colored green by default
Print a traceback message for all serious exceptions
@comment supports comments starting with REM
Fixed bugs (one crasher)
Fixed crasher in atFile.scanAllDirectives
Fixed syntax coloring bug involving Leo directives
Windows now open without visual problems
Made Convert Tabs, Convert All Blanks and Convert All Tabs undoable
Made Prefs panel non-modal
leo.py 2.5.1 Many improvements
New & improved commands
Tabs and spaces
Added Convert All Blanks and Convert All Tabs commands
Added Convert Tabs command
Tab widths work
Negative tab width converts tabs to spaces
Improved Preferences panel
Using leoConfig.leo and leoConfig.txt
Cut and Paste now works properly in all situations
Added Recent Files menu
All Extract and Import commands are now undoable
Made Cascade and Minimize All commands functional
Significant bug fixes
Fixed several bugs in the Import commands
Invalid directories no longer cause read errors
Fixed crashers
Fixed crasher when saving a new file
Fixed crashers in Remove Sentinels command
Fixed crasher in Import commands
Fixed minor bugs & improvements
Recolored body text after extract commands
Fixed bug involving missing clone mark after Move Right command
Changed defaultextension from "x" to ".x" in dialogs.
Fixed minor bug in Save and Save As commands
Fixed minor problems with documentation
Renamed Font to Set Font... and Syntax Coloring... to Set Colors...
leo.py 2.4
Fixed several bugs
Added better error recovery when writing .leo files
Dragging headlines
How to associate leo.py with .leo files on Windows
leo.py 2.3
leo.py 2.2 Bug fixes and documentation revised
2.1 Import & Export commands
Import and Export commands
Fixed moves up to root position
Improved the syntax colorer
2.0 Untangle, Unlimited Undo/Redo, Syntax Coloring, etc.
Format change: no blank lines between sentinels in derived files
Unlimited Undo and Redo
Drawing and syntax coloring
Syntax coloring works for all languages
Fixed minor syntax coloring bugs
Headlines given more space
Suppressed auto-indent for Python in @nocolor mode
Tangle/Untangle
Fixed old bugs in Untangle
@c now is the same as @code
Untangle only trims trailing lines from updated nodes
Fixed Linux-specific bugs
1.0 New commands and bug fixes
Unfinished commands
How to copy an outline from Borland Leo to Leo.py
Fixed bugs
Remaining minor problems
0.08 Find, Change and Tangle commands & major bugs fixed
About the Find and Change commands
About the Tangle command
About hyperlinking
Fixed significant bugs
Other improvements
Improved memory management
The trace function (Sherlock tracing)
Improved directory and file name handling
Improvements that will be propagated back to LeoCB
0.07 Stability and scripting
Thorough testing
Removed many irritations. Some remain
Fixed bug that can drop data
Added Python window and scripting support
Added Script to convert from Leo 1 to Leo 2
Added Leo Documentation command
Added Auto indent
0.06 Minor changes for Linux
0.05 The first functional version
Testing & Performance
Installing and Running leo.py
Files & Folders
Functional Commands and Features
Non-Functional Commands and Features
Bugs, Problems and Code Quality
New Things To Do
Borland PC versions
Borland 2.7 Major bug fix: please upgrade
Borland 2.6 Major changes: please upgrade
Format change: no blank lines between sentinels in derived files
Eliminated spurious read errors
@c now is the same as @code
Fixed old bugs in Untangle
Compatibility with leo.py v2.0
Changed "Batch" check box to "Show Context"
Can not ensure .leo extension
version 2.5: Fixed several serious bugs
version 2.4: The new Leo2 (Do not use this version)
Fixed clone bugs
.leo files now contain all data
Replaced archive command
Automatic update of @file trees
Errors while reading
Errors while writing
Drawbacks of @file trees
version 2.3: Do not use this version
@first directive
Archive Entire Outline command
Fixed @c syntax coloring
Fixed bugs in the find command
Fixed bug involving @file dirty bits
The Log window is now part of the Leo window
version 2.2 (beta)
version 2.1 (beta)
1.x versions
Changes made in version 1.15
Changes made in version 1.14
Changes made in version 1.13
Changes made in version 1.12
Changes made in version 1.11
Multiple parts not allowed for << section name >>
Untangling: updating an outline
Changes made in version 1.10
Changes made in version 1.9
New Execute Script command
Python window improved
Changes made in version 1.8
Changes made in version 1.7
Changes made in version 1.6
Changes made in version 1.5
Changes made in version 1.4
Outline To Noweb command
find/change Python scripts
v.commands() Python method
Changes made in version 1.3
Changes made in version 1.2
Changes made in version 1.1
Changes made in version 1.0
Changes made in version 0.19
Fixed directory bug
Added @language HTML, perl and perlpod
Eliminated @noweb & replaced @cweb with @language cweb
About cweb mode
Improved syntax coloring
Improved Print command
Improved how Untangle handles block comments
Eliminated clone windows (but not clone nodes!)
Eliminated Extended noweb Syntax
Changes made in version 0.18
File format extended
Fixed bug in languages without block comments
Added @header and @noheader directives
Added @language fortran90
Added @language perlpod
Changes made in version 0.17
Support for noweb <<*>> convention
new @cweb, @noweb, @path, @tabwidth and @pagewidth directives
CWEB mode now has a chance of working
Reduced default screen size
Changes made in version 0.16
Bug fixes
Fixed crasher involving switching windows
new @language directive
@language and @comment directives can be inherited
Untangle can now handle non-C files
Untangle now removes trailing blank lines
new @verbose, @terse and @silent directives
Default directories
Import Files command now handles Pascal , Python and Java files.
Toggle Active Pane command
Path delimiters are now '\' instead of '/'
Cleaned up code
Fixed crasher involving empty section names
Untangle warns about sections not in the outline
A limitations involving Untangle
Known Bugs & Limitations
Users Guide
An overview of Leo
Front material
Title Page & Summary
Preface
Leo's Design Goals
Acknowledgements
Legal Notices: Leo uses the Python license
Chapter 1: Introducing Leo
Overview
Leo's main window
Body text and sections
Outlines organize sections
removeSentinelsCommand
<< Read file into s >>
<< set delims from the header line >>
<< Write s into newFileName >>
Clones manage tasks
Outlines generate derived files
Chapter 2: Installing Leo
System requirements
How to install leo.py on Windows
How to associate leo.py with .leo files on Windows
Quick start
Chapter 3: Using Outlines & Body text
Cloning headlines
read me
a
b
c
d
e
c
d
e
g
b
c
d
e
c
d
e
a
b
c
d
e
c
d
e
g
b
c
d
e
c
d
e
f
Creating and destroying headlines
Cutting, pasting and deleting headlines
Dragging headlines
Editing body text
Expanding & contracting headlines
Indenting body text automatically
Optional Emacs-style auto-indent
Marking headlines
Moving & Reorganizing headlines
Opening URL's automatically
@url
Resizing panes
Undoing operations
Chapter 4: Writing Programs in Leo
Overview
Sections and section references
Organizing @file trees.
Examples
@file f.c
<< constants >>
<< declarations >>
function 1
function 2
@file classX.py
<< imports for classX >>
Getters
getter 1
getter 2
Setters
setter 1
setter 2
@file mainClass.py
<<mainClass imports >>
<<class mainClass>>
method 1
method 2
<<class helperClass>>
method 1
method 2
@file bad
Methods
<< section 1 >>
A
More about directives
@color and @nocolor control syntax coloring
@comment sets comment delimiters in @root and @unit trees
@delims directive specifies comment delimiters in @file trees
@encoding directive specifies encoding of text
@first and @last directives allow leading and trailing lines in @file trees
@language specifying the target language
@lineending directive specifies line endings
@path, @pagewidth and @tabwidth directives set preferences
@raw and @end_raw directives delimit raw text
About cweb mode
Appendix 1 to Chapter 4: Using @rawfile, @silentfile and @nosentinlefile trees
Appendix 2 to Chapter 4: Using the @root directive, Tangling and Untangling
@root vs. @file trees
Sections and section definitions
Tangling @root trees with the Tangle commands
Untangling @root trees with the Untangle commands
@verbose, @terse, @quiet and @silent directives control comments in @root trees
Chapter 5: Using Leo's Commands
The File Menu
Loading, Saving and Reverting Files
Communicating with external editors
Open With command
createOpenWithMenuFromTable
Tangling an outline: Producing Derived Files
Untangling: updating an outline
Tangling and Untangling: the Interface with the IDE
Importing Files into Leo Outlines
Exporting Files from Leo Outlines
Quitting Leo
The Edit Menu
Undoing changes
Cutting, pasting and selecting text
Shifting body text
Creating nodes from body text
Converting leading blanks and tabs in body text
Executing Python scripts in body text
Finding and changing text
Batch
Ignore Case
Mark Changes
Mark Matches
Pattern Match
Reverse
Search Body Text
Search Headline Text
Suboutline Only
Whole Word
Wrap Around
Finding locations in outlines corresponding to line in derived files
Go To Line Number command
Inserting the date and time
Reformatting paragraphs in body text
Reformat Paragraph command
Matching brackets and parenthesis
Setting Preferences
PHP keywords
LaTex keywords
The Outline Menu
Creating and deleting headlines
Cutting, pasting and deleting headlines
Expanding and contracting headlines
Moving, sorting and reorganizing headlines
Marking headlines
Cloning headlines
read me
a
b
c
d
e
c
d
e
g
b
c
d
e
c
d
e
a
b
c
d
e
c
d
e
g
b
c
d
e
c
d
e
f
The Window Menu
Resizing panes
Cascading & minimizing windows
Toggling the active pane
Toggling the orientation of the Leo window
The Help Menu
Getting Leo's version
Reading documentation
Configuring Leo
Chapter 6: Leo and Literate Programming
Why I like Literate Programming
Literate programming creates a new design dimension
Design and coding happen at the highest possible level
The visual weight of code is separate from its actual length
Sections show relations between snippets of code
Complex section names invite improvements
Sections create a place for extensive comments
Section names eliminate mundane comments
Sections create comments automatically
Literate programming clarifies the shape of functions
I spend less time formatting code
How Leo Improves Literate Programming
Outlines add context
Outlines provide scope for commands
Clones create different views and focus attention
Outlines increase flexibility
Outlines express hierarchy directly
Outlines create new design dimensions
Leo improves tangling and adds untangling
How Leo Changes the Notion of Literate Programming
Chapter 7: Scripting Leo with Python
Overview
Basic scripts
Getting commanders and frames
Redirecting output from scripts
Getting the headline and body text of the presently selected node
Setting the headline and body text of the presently selected node
Traversing outlines
Updating the screen
Invoking commands from scripts
Getting preferences
Setting preferences
Functions for finding and changing text from scripts
Example 1: the findAll function
Example 2: the reFindAll function
Summary of the vnode class
Getters
Setters
An example script: children(v)
Functions defined in leoGlobals.py
Appendix to Chapter 7: Scripting the Borland version of Leo
Installing Python
Leo's Python Window
Functions
topCommand
getCommands
getLeoPyWindow
The Commands class
Updating the screen
File menu commands
Edit menu commands
Outline menu commands
Window menu commands
Menu enablers
Commands Utilities
The LeoPyWindow class
The Preferences class
The vnode class
Getters
Setters
Chapter 8: Customizing Leo
Using leoConfig.leo and leoConfig.txt
Using plugins and hooks
New plugin architecture
@file plugins/mod_open_with.py
on_idle
<< update v's body text >>
<< set s to the new text >>
<< set conflict flag >>
create_open_with_menu
<< create the Open With menu >>
About hooks
SECURITY WARNINGS
The danger of trusting code in shared .leo files
NEVER use this kind of code in a hook!!
Don't use rexec !!
<< change all instances of rexec to exec in v's body >>
<< delete thisNode >>
<< clear the undo stack >>
Convenience routines for plugins
Convenience functions to enable and disable the "idle" hook
Convenience methods to create, destroy and modify menus
createMenuItemsFromTable
createNewMenu
createOpenWithMenuFromTable
deleteMenu
deleteMenuItem
Example: how to create a menu
Translating menus into other languages
Putting the Leo icon in Leo windows
Chapter 9: History of Leo
Beginnings
Versions, and more versions
Designing @file trees
Deciding to do Leo2
A prototype
User interaction
The write code
The read code
The load/save code
Attributes, mirroring and dummy nodes
Clones
Error recovery, at last
Chapter 10: Theory of Operation
Overview
Nodes
Drawing and events
Clones
Find and change commands
Tangle and Untangle commands
Unicode theory of operation
Unlimited undo
Appendices
Code hidden in leo.py
c2py.py
disStats.py
Tkinter utilities
Font dialog
Syntax coloring code
Tree code
Converting @root trees to @file trees
Directives
@c and @code
@chapter (Macintosh only)
@color
@comment
@delims (not valid in @root or @unit trees)
@doc and @(space)
@file (not valid in @root or @unit trees)
@first (not valid in @root or @unit trees)
@ignore (not valid in @file trees)
@language
@nocolor
@others (valid only in @file trees)
@pagewidth
@path
@root (not valid in @file trees)
@section (Macintosh only)
@silent (not valid in @file trees)
@tabwidth
@terse (not valid in @file trees)
@unit (not valid in @file trees)
@verbose, @terse and @silent directives
Error Messages
Errors while tangling
Can not re-open temp file
Can not rename temporary file name
@code expects the header: [text of header] to contain a section name
@directive not valid here
Halting Tangle: too many errors
Invalid recursive reference of << section name >>
Multiple parts not allowed for << section name >>
No file written because of errors
Run on comment
Run on file name in @root directive
Run on section name
Run on string
Section definition not valid here.
Sections nested too deeply
The outline contains no roots
Undefined section: <<< section name >>>
Unexpected @directive while putting code
Warning: possible duplicate definition of: <<< section name >>>
Warning: <<< section name >>> has been defined but not used
Errors while untangling
Incompatible definitions of <<< section name >>>
Missing root part
Missing root section
Missing sentinel line for <<< section name >>>
Unterminated section: <<< section name >>>
Errors while reading @file nodes
Minor errors
Serious errors
Errors while writing @file nodes
Formats of Files Derived from @file
Sentinels in derived files
The @delims directive
@leo & comment delimiters
@node
@body
@at and @doc & the trailing whitespace convention
@others
@@delims
@@<text>
@verbatim and @verbatimAfterRef
@<<section name>>
Future Features
Known Bugs & Limitations
Reference for @file trees
Macintosh Notes
Differences Between the PC and Mac Versions
Transferring Files
Sherlock
Unicode
How Leo handles unicode
Unicode settings
XML file format
<?xml ... ?>
<leo_file> ... </leo_file>
<leo_header ... />
<globals ... > ... </globals>
<preferences ... > ... </preferences>
<find_panel_settings ... > ... </find_panel_settings>
<vnodes>...</vnodes>
<v...> ... </v>
<tnodes> ... </tnodes>
<t...> ... </t>
<clone_windows> ... </clone_windows>
<clone_window ...> ... </clone_window>
DTD for Leo's File Format
An example file
The file itself
The outline from which the example file was created
Headline 1
Headline 2
Headline 3 ( No body text)
Headline 4 ( a clone )
Headline 5
Headline 4 ( a clone )
Headline 5
FAQ
Learning Leo
What's the best way to learn to use Leo?
Where are the simple examples?
Creating and using derived files
How should I choose between @root and @file trees?
I don't want sentinels in my derived files. What should I do?
Appendix 1 to Chapter 4: Using @rawfile, @silentfile and @nosentinlefile trees
I don't want to Leo to expand sections. What should I do?
Appendix 1 to Chapter 4: Using @rawfile, @silentfile and @nosentinlefile trees
How can I create Javascript comments?
How do I make derived files start with a shebang line?
I want to use Leo to create cweb files. What should I do
About cweb mode
Can @file trees contain material not in the derived file?
Customizing Leo
I'm having trouble getting customizeLeo.py to work. What should I do?
How do I use customizeLeo.py to modify Leo?
How can I add support for a new language?
Chapter 8: Customizing Leo
Using leoConfig.leo and leoConfig.txt
Using plugins and hooks
New plugin architecture
@file plugins/mod_open_with.py
on_idle
<< update v's body text >>
<< set s to the new text >>
<< set conflict flag >>
create_open_with_menu
<< create the Open With menu >>
About hooks
SECURITY WARNINGS
The danger of trusting code in shared .leo files
NEVER use this kind of code in a hook!!
Don't use rexec !!
<< change all instances of rexec to exec in v's body >>
<< delete thisNode >>
<< clear the undo stack >>
Convenience routines for plugins
Convenience functions to enable and disable the "idle" hook
Convenience methods to create, destroy and modify menus
createMenuItemsFromTable
createNewMenu
createOpenWithMenuFromTable
deleteMenu
deleteMenuItem
Example: how to create a menu
Translating menus into other languages
Putting the Leo icon in Leo windows
Other topics
When should I use clones?
How can I cut and paste nodes between different copies of Leo
How can I add support in Leo for a new language?
code
<< define global constants >>
<< Create the Target Language frame >>
<< define colorizer keywords >>
<< configure language-specific settings >>
<< check for syntax coloring prefs >>
<< initialize constant ivars >>
set_delims_from_language
set_language
colorizeAnyLanguage
<< configure tags >>
<< configure language-specific settings >>
Multiline State Handlers
<< continue doc part >>
<< handle cweb doc part >>
<< handle noweb doc part >>
<< continue python triple string >>
<< continue block comment >>
<< continue nocolor state >>
<< handle string >>
<< start block comment >>
<< handle single-line comment >>
<< handle C preprocessor line >>
<< handle possible section ref or def >>
<< set the hyperlink >>
<< handle possible @keyword >>
<< Handle all cweb control codes >>
<< Handle non-cweb @keywords >>
<< handle possible keyword >>
<< handle blank >>
<< handle tab >>
<< handle normal character >>
getPrefs
<< check for syntax coloring prefs >>
@root tcl/tk test
<< stuff >>
How can I create a template .leo file?
Help! I can't seem to generate files after using the Import commands.
Glossary
@file node, @others node, @root node, etc.
@file tree, @others tree, @root tree, etc.
Body pane
Body text
Body text box
Child
Chunk
Clone
Clone Arrow
Code section
CWEB
Demote
Derived file
Descendent
Directive
Doc section
Escape convention
Grandchild
Headline
Leo, Leo1 and Leo2
Literate programming
Marking
Named nodes
Node
noweb
Offspring
Organizing node
Orphan node
Outline
Outline pane
Parent
Part
pdf file
Promote
Root
Scope
Section
Section name
Section reference
Sentinels, sentinel lines
Sibling
Status Icon
Tangling
Target language
Tree
Unnamed node
Untangling
Weaving
Sample Code
Hello World using @root
<< includes >>
<< methods >>
Hello World using @file
@file sampleHello.c
<< includes >>
main
Examples of using @root trees
Nodes
tnode
tnode.h
<< tnode data members >>
<< tnode constructors & destructors >>
<< tnode debugging >>
<< tnode getters >>
<< tnode setters >>
tnode.cpp
<< tnode includes >>
<< tnode abbreviations >>
Birth & Death
Debugging
edump
Getters
Getting body text and selection
bodyRTF
bodyCString
bodyCStringLength
bodyString (tnode)
loadBodyPaneFromTnode
selectionLength
selectionStart
Status bits
isDirty
isRichTextBit
isVisited
cloneIndex
fileIndex
hasBody
joinHead
Setters
Setting body text
saveBodyPaneToTnode
setTnodeRTF
setTnodeText
setSelection
Status bits
clearDirty
clearRichTextBit
clearVisited
setDirty
setRichTextBit
setVisited
setCloneIndex
setFileIndex
setJoinHead
vnode
vnode.h
<< dvnode data members >>
<< vnode access methods >>
<< vnode comparisons >>
<< vnode constructors & destructors >>
<< vnode data members >>
<< vnode debugging >>
<< vnode file methods >>
<< vnode getters >>
<< vnode private methods >>
<< vnode setters >>
<< vnode tree methods >>
vnode.cpp
<< vnode includes >>
<< vnode abbreviations >>
Birth & Death
Comparisons
atFileNodeName
isAtFileNode
isAtIgnoreNode
isAtOthersNode
matchHeadline
Dumping
edump
description
File Conversion
convertTreeToString
moreHead
moreBody
Getters
Children
childIndex
firstChild
hasChildren
lastChild
nthChild
numberOfChildren
Cross-file clones
xCloneName
xCloneIndex
Structure Links
back
lastNode
level
next
parent
nodeAfterTree
threadBack
threadNext
visBack
visNext
Head & Body Strings
bodyCString
bodyCStringLength
bodyRTF
bodyString
headCString
headCStringLength
headString
Status Bits
isCloned
isDirty
isDummy
isExpanded
isExpandedBitSet
isMarked
isOrphan
isSelectedBitSet
isTopBitSet
isVisible
isVisited
status
VCL classes (vnode)
findRoot
commands
leoForm
treeNode
treeNodes
treeView
currentVnode (vnode)
iconVal
isAncestorOf
isRoot
joinList
selectionStart
selectionLength
t
Setters
cross-file clones
setXCloneIndex
setXCloneName
headline and body text
appendStringToBody
setBodyStringOrPane (new for SWIG)
setHeadString & initHeadString
setHeadStringOrHeadline (new for SWIG)
trimTrailingLines
icons
computeIcon
setIcon
setIconVal
setIconWithVal
setJoinList
setSelection
setT
setTreeNode
status bits
clearClonedBit
clearDirty & clearDirtyJoined
clearDummy
clearMarked
clearOrphan
clearVisited
initClonedBit
initDirtyBit
initExpandedBit (new)
initMarkedBit
initSelectedBit (new)
initTopBit (new)
initStatus
setAncestorAtFileNodeDirty
setClonedBit
setDirty & setDirtyJoined
setDummy
setOrphan
setVisited
setMarked
Moving, Inserting, Deleting, Cloning
Entry Points (vnode)
clone (vnode)
createDependents
doDelete
<< dump the dv list >>
freeDVNodes
insertAfter
insertAsLastChild
insertAsNthChild
moveAfter
moveToRoot
moveToNthChildOf
restoreOutlineFromDVnodes
<< Insert v into the outline >>
<< Set back and parent fields for the new vnode >>
<< set icons for all vnodes of the result tree >>
cloneTree
copyCloneBitsTo
copyTree
destroyDependents
destroyTree
invalidOutline
isJoinedTo
isOnJoinListOf
joinNodeTo
joinTreeTo
saveOutlineWithLevel
<< Set the data fields of dv >>
shouldBeClone
unjoinNode
<< Set prev to the node that points to this >>
unjoinTree
validateOutlineWithParent
<< validate parent ivar >>
<< validate childIndex ivar >>
<< validate x ivar >>
Examples of using @file trees
atFile
@file sampleAtFile.h
<< declare public utility functions >>
<< private atFile data >>
<< private read methods >>
<< private sentinel methods >>
<< private utilities >>
<< private write methods >>
@file sampleAtFile.cpp
Declarations
<< atFile includes >>
<< atFile abbreviations >>
<< atFile constants >>
Commands methods
Commands::readAtFile
Commands::writeAtFile
atFile ctor
Reading
createNthChild
joinTrees
read
<< Declare read vars >>
<< read the entire file into fileData >>
<< Scan the file buffer >>
<< Issue structure error message >>
<< Delete root's tree and body text >>
<< Handle clone bits >>
<< Join cloned trees >>
<< Handle all status bits >>
<< Issue serious error message >>
readAll
scanDoc
<< Skip the opening sentinel >>
<< Skip an opening block delim >>
<< Append the next comment line to v's body >>
<< Skip the closing sentinel >>
<< Remove a closing block delim from v's body >>
scanHeader
scanText
<< Set lineIndent and linep >>
<< remove indent and append line >>
<< handle an ending sentinel >>
<< handle unexpected end of text >>
start sentinels
<< scan @+at >>
<< scan @+body >>
<< scan @+doc >>
<< scan @+leo >>
<< scan @+node >>
<< skip the node sentinel, setting headline, ref and n >>
<< Set status bits in dummy vnode >>
<< set index from the C=nnn, field >>
<< copy new_ms to child's body >>
<< Check the filename in the sentinel >>
<< scan @+others >>
unpaired sentinels
<< scan @ref >>
<< scan @verbatim >>
<< scan @@ >>
<< warn about unknown sentinel >>
Writing
putBody
putBodyPart (removes trailing lines)
<< put code/doc parts and sentinels >>
putCodePart & allies
<< handle the start of a line >>
<< compute delta, the width of the whitespace >>
<< handle @others >>
<< put @verbatim sentinel if necessary >>
<< put the line >>
<< put possible section reference >>
findReference
inAtOthers
putAtOthers
putAtOthersChild
putDirective
putDoc
<< skip the @<space> or @doc >>
<< set limit to the end of the doc part >>
putDocPart
<< copy words, splitting the line if needed >>
write
<< Open files. Set orphan flag and return on errors >>
<< setOrphan and return if mTargetFileName is bad >>
<< Open a temp file and set mOutputFileName >>
<< Warn about @ignored and orphans >>
<< Issue final write error message >>
<< Replace the target with the temp file if different >>
writeAll
<< Clear all orphan bits >>
Output
putBlanks
putChar
putIndent
putInt
putString
putTabs
Sentinels
nodeSentinelText
<< append the status-bit characters to s >>
putCloseNodeSentinel
putCloseSentinels
putOpenLeoSentinel
putOpenNodeSentinel
putOpenSentinels
putSentinel
sentinelKind
sentinelName
skipSentinelStart
Utility functions
compareFiles
directiveKind
isSectionName
setAncestorAtFileNodeDirty
Utilites
appendToBody
atFile::scanAllDirectives
<< Set global arguments to default values >>
<< Set path from @file node >>
<< Test for @comment or @language >>
<< Test for @path >>
<< Remove leading and trailing delims if they exist >>
<< Test for @pagewidth and @tabwidth >>
<< Set current directory if possible >>
<< Set comment Strings from global vars >>
error
skipIndent
updateCloneIndices
writeError
FileCommands
@file sampleFileCommands.cpp
Declaratons
<< File commands includes >>
<< Declare File commands private functions >>
<< Declare the EBadLeoFile class >>
Tag List routines
deleteTagList
getVtag
newVtag
Export
exportMoreText
flattenOutline
flattenOutlineWithFilename
outlineToNoweb
convertToNoweb
convertCodePartToNoweb
<< Output the section reference >>
<< Set name to the file name p2..p >>
<< point p1 and p2 at section name in v's headline >>
convertDocPartToNoweb
copyPart
<< return p if this is a section definition >>
Import
convertMoreStringsToOutlineAfter
<< Declare the vars for convertMoreStringsToOutlineAfter >>
<< Link a new vnode v into the outline >>
<< Set the headline string, skipping over the leader >>
<< Count the number of following body lines >>
<< Add the lines to the body text of v >>
importMoreText
<< Read the file into strings >>
moreHeadlineLevel
stringsAreValidMoreFile
Reading
fileInfo class
<< Declare the fileInfo class >>
Birth & Death
createVnode
<< Create a root vnode >>
finishOutline (alert)
<< Set attributes and icons for all nodes >>
<< Create join lists of all vnodes >>
<< Set the current node >>
finishPaste (creating join lists could be a problem)
<< Check number of created tnodes >>
<< Set attributes and icons for all pasted nodes >>
<< Create join lists of all pasted vnodes >>
<< Recompute clone bits for pasted vnodes >>
getT
newCloneWindow
newTnode (alert)
setMaxTnodeIndex
setNumberOfTnodes
get routines
get (basic)
getClipboardHeader
getCloneWindows
getEscapedString (uses large buffer)
<< allocate mTextBuf >>
<< increase the size of mTextBuf >>
getFindPanelSettings
<< Set defaults of all flags >>
getGlobals
<< set the window position >>
<< set the log window position >>
getLeoFile (reads all @file nodes)
getLeoHeader
getLeoOutline
getPosition
getPrefs
<< check for syntax coloring prefs >>
<< Set the default directory preference >>
<< Set the TSyntaxMemo options >>
getSize
getTnodes
getTnode (alert)
getVnodes
getVnode
<< Handle vnode attribute bits >>
skipWs
skipWsAndNl
open
<< Create the TFileStream and the file buffer >>
<< Set the default directory >>
<< Delete the TFileStream and the file buffer >>
readLine
<< reallocate mLineBuf >>
<< fill mFileBuf or set mFileEmptyFlag >>
<< return the last line, if any >>
xmlUnescape
Writing
assignFileIndices
compactFileIndices
shouldCompactOnSave
<< Compute the number of tnodes used >>
put routines
put (basic)
<< put s to the mMemoryStream >>
<< put s to mFileStream >>
putClipboardHeader
<< count the number of tnodes >>
putCloneWindows
<< put position of clone window >>
putEscapedString
putFindSettings
putGlobals
<< put the body/outline ratio >>
<< put the position of this form >>
<< put the position of the log window >>
putHeader
<< count the number of tnodes and clone windows >>
putLeoOutline (writes to clipboard)
putPrefs
<< put syntax coloring prefs >>
<< put default directory >>
putProlog
putPostlog
putTnodes
put ( tnode * )
putVnodes
put ( vnode * )
<< Put tnode index if this vnode has body text >>
<< Put a vnode tag if this vnode has a clone window >>
<< Put attribute bits >>
<< write the head text >>
save
saveAs
saveACopyAs
saveOutlineOnlyTo
xmlEscape
write_LEO_file
<< Open fileName and buffer it >>
<< Close the file >>
White papers
Recovering from Read Errors
CVS postings
How to use Leo with CVS
Leo can't mend clone links
Problems
.cvs.leo format
@cvs
tk is the future of Leo
Why I like Python
@nocolor

Leo is:

- A programmer's editor, an outlining editor and a flexible browser. - A literate programming tool, compatible with noweb and CWEB. - A data organizer and project manager. Leo provides multiple views of a project within a single outline. - Fully scriptable using Python and saves its files in XML format. - Portable. Leo.py is 100% pure Python. leo.py requires Python 2.2 and tcl/tk 8.3 or above. - Open Software, distributed under the Python License.
@nocolor

* Leo's main window contains two panes, an outline pane on top and a body pane below.

* The outline pane works like Windows Explorer: Headlines expand and contract when you click the + or - icon.

* The other icon shows the status of the headline. See the subheadlines of this headline for details.

* You can change the relative sizes of the panes by dragging the splitter area between the two panes.
This is the body text of this node. Nodes containing body text are marked with a small blue box in the status icon.
You can mark headlines in several ways: with the various Outline:Mark commands, with the Find or Change commands, or with the Untangle command. The "Go To Next Marked" command will go to the next marked node, if any.
Cloned nodes share the same body text. Also, the headlines of all cloned headlines change whenever a change is made to any of them.
@nocolor

Please use Leo's SourceForge forums at

http://sourceforge.net/forum/?group_id=3458

to ask questions or suggest improvements. That way other people can share in the discussions.
@nocolor

Leo owes much of its visual design to MORE, possibly the most elegant computer program ever written. Leo's clone nodes are inspired by MORE.

The following people reported bugs, answered questions, and made suggestions for improving Leo: Alex Abacus, Shakeeb Alireze, Bruce Arnold, Chris Barker, Eric Brown, Darius Clarke, Bill Drissel, Wenshan Du, Allen Edwards, Chris Elliot, Mark Engleberg, Dethe Elza, Stephen Ferg, Tom Fetherston, Tomaz Ficko, Niklas Frykholm, Fred Gansevles, Jonathan M. Gilligan, Thomas Guettler, Zak Greant, Thomas Guettler, Romain Guy, Dave Hein, Tiago Castro Henriques, Gary Herron, Steve Holden, Klass Holwerda, Matthias Huening, Robert Hustead, John Jacob, Christopher P. Jobling, Eric S. Johansson, Garold Johnson, James Kerwin, Nicola Larosa, David LeBlanc, Chris Liechti, Steve Litt, Martin v. Loewis, Robert Low, Fredrik Lundh, Alex Martelli, Marcus A. Martin, Gidion May, David McNab, Chad Netzer, Naud Olivier, Joe Orr, Marc-Antoine Parent, Paul Paterson, Davide Salomoni, Sean Shaleh Perry, Tim Peters, Scott Powell, Bruce Rafnel, Walter H. Rauser, David Speed Ream, Rich Ries, Aharon Robbins, Guido van Rossum, David Rowe, Steven Schaefer, Wolfram Schwenzer, Gil Shwartz, Paul Snively, Jurjen Stellingwerff, Jeffrey Thompson, Gabriel Valiente, Tom van Vleck, Jim Vickroy, Tony Vignaux, Ying-Chao Wang, Cliff Wells, Dan Wharton, Wim Wijnders, Vadim Zeitlin.

The following people have written code for Leo: Dave Hein wrote code to support PHP, to handle Tk version numbers properly and to open the Python window properly on Linux. Marcus A. Martin wrote code to support LaTex. Paul Paterson wrote the first implementation of plugins. Steven P. Schaefer eliminated problems with end-of-line on Linux and fixed bugs in how directives were handled in @root trees.

The following deserve special mention: David Brock wrote TSyntaxMemo. The late Bob Fitzwater kept me focused on design. Donald Knuth invented literate programming and the CWEB language. Joe Orr created Leo stylesheets for Leo; see http://www.jserv.com/jk_orr/xml/leo.htm. Joe Orr also created an outstanding set of tutorials for Leo; see http://www.evisa.com/e/sb.htm. John K. Ousterhout created tcl/Tk. Neal Norwitz wrote PyChecker. Marc-Antoine Parent urged me to use XML for Leo's file format and helped improve it. Paul Paterson suggested the plugin architecture and suggested an approach to spell checking. Norman Ramsey created noweb and gave permission to quote from the noweb web documentation. Rich Ries has contributed a huge number of suggestions. Steven P. Schaefer pointed out major security problems lurking in hooks. Gil Shwartz helped with unicode support. Phil Straus has been a great friend and constant support. Guido van Rossum created Python, Tkinter and the Python License. Dave Winer created MORE. Dan Winkler helped support Leo on the Mac.

Special thanks to my family. My brother, David Speed Ream, tested Leo and made many useful suggestions. Rebecca, James and Linda make it all worthwhile. It was during a conversation with Rebecca that I realized that MORE could be used as a prototype for Leo. That was a crucial first step.
@nocolor Here is what people are saying about Leo:
From Dan Winkler, heydan@post.harvard.edu

This year my accountant is getting a beautiful printout generated by LaTeX and Leo. I have a complicated tax situation this year, but I got it all laid out and organized in Leo. Then I had each of the nodes that had something my accountant needs to see write the data out to a file in the form a LaTeX table.

Sometimes a row of a table would have a result that was calculated by adding up a list of numbers. For that I used the modern day equivalent of an adding machine paper tape -- I stored a lisp s-expression in a Leo comment. I like s-expressions for this because once I put the opening "(+" on one line and the closing ")" on another line, I can fill in additional numbers just by typing them and can even annotate them with comments. So in the middle of generating a LaTeX file I might have something like this:

@ (+ 1165.26 1823.70 ; May 2002 123.38 ; June 2002 13.50 ; July 2002 13.21 ; October 2002 55.25 ; November 2002 ) @c

That's an annoted record of how I arrived at the number the accountant will actually see. I can just paste it into any lisp or scheme interpreter and get the total. Adding additional numbers is easy.

For next year, I think I might take this a step further. What I did this year is good for adding up numbers to get a total for one row of a LaTeX table. But it turns out I'd also like some more processing done on those tables (which I had to do by hand this time) -- I'd like the rows sorted in reverse order by magnitude (so that the big numbers jump out at you from the start of the tables) and I'd like a total of all the rows in the table. So I think next year, instead of having an s-expression that computes the total of one row for me, I think I'll use s-expressions that generate whole tables, formatted for LaTex, from the underlying data. So I'm thinking next year my s-expressions might look more like this:

@ (table "Widget Related Expenses" ("widget insurance" (+ 1165.26 1823.70 ; May 2002 123.38 ; June 2002 13.50 ; July 2002 13.21 ; October 2002 55.25 ; November 2002 )) ("widget shipping" (+ 472.15 651.94 ; May 2002 54 ; June 2002 )) ("widget cleaning" (+ 165.26 183.70 ; May 2002 123.38 ; June 2002 13.50 ; July 2002 13.21 ; October 2002 55.25 ; November 2002 )) ) @c

The job of that "table" function would be to return the LaTeX code needed to display a table with the category names and values, sorted descending by magnitude, with the total displayed. It's sort of a poor man's way of doing a spreadsheet inside Leo and then making it look great using LaTeX. The idea would be as I wanted to add more data, I'd add it to the s-expression and then reevaluate the whole thing by pasting it into a lisp interpreter and then copying the result back into the same Leo node for LaTeX to process.

-- Dan
Leo has simplifyed updating and creating new scripts and .bats keeping similar information in the same place. there is almost an addictive withdrawl effect when I can complete an operation in so much less time with leo & python than I had become used to.
@nocolor September 3, 2002

Hello, my full name is David Speed Ream. I am known as Speed to friends and enemies alike, but I gladly answer to David or most any other handle. I am an unabashed and biased fan of Leo, the fact that it was written by my brother Edward only slightly coloring my already colored glasses. I have been testing and using Leo in software production for over 4 years. My company currently has over 50,000 lines of code in over 100 source files that are written using Leo.

My comments are from two points of view, the first being software project manager for a complicated, multi-module software product, and the second being as a production line coder. For me, Leo’s greatest and only real drawback is the learning curve. This learning curve can be shallow is if all that is required is that someone code using Leo. However, in our company we allocate 40 to 80 hours _on top_ of the normal coding load for someone to come up to speed on Leo. The ROI (return on investment) is calculated by me to be on the order of 3 months. So if I hire a consultant for less than 3 months, I don’t teach him Leo, even though all source code in our company must reside in Leo files for the reasons I won’t go into now.

I consider that my coders are 15 to 30 percent more efficient in their daily operations than my competition’s people. This indefensible claim of mine is based on the changes in my productivity as Leo grew from a test document production tool to the primary production method for all our assembly, c and cpp source code.

Personally, I hate to deal with documentation when I write code, except: 1) When I am first sitting down to solve a new problem. Then the documentation becomes quite long-winded and pontificatory, as if I were the only one on earth smart enough to solve the problem - or 2) When I come back to code I or someone else has written and find the documentation insufficient to understand the code without study (seems to be most of the time).

So I do not require my engineers or myself to do a great job of documentation, nor do I use Leo for that purpose. Rather, it is Leo’s outlining and organizing ability, and Leo’s ability to create source files from within the outline that give me what I think is a tremendous competitive advantage. Each of my company’s products run on all versions of windows from Win 3.1 to XP. In our flagship software piece, there are ten main modules, and each module is maintained by one single Leo file. In the CODEC module, one Leo file named compress.leo organizes and creates seven .asm files, forty-four .c files, twenty .h files, two .def files, four .mak files, etc. etc. etc. This one file can be checked out from source code control and given to an engineer for the addition of a new feature.

In it are contained all the known issues for the CODEC, each issue arranged in its own clone section. One clone section groups together every routine, variable or type definition that must change between different versions of Windows. These sections could be from six different c source files, two assembly files, and eight .h files. Another clone section groups together those sections relating to memory problems, which change according to the memory configuration and TSR configuration (or lack thereof) on the target machine. Another clone section groups sections that fail (or don’t fail) if the routine in question was accidentally run during the dreaded ‘interrupt time’. Another clone section is a section containing clones, each of which is named after the major bug that was fixed when the engineer who fixed the bug grouped a bunch of routines, definitions, etc. together to fix the bug.

None of the above clone sections was ‘designed’ into the document. Just the opposite happens. When the codec was first written, there was just a single Leo file with a bunch of sections for each c routine or assembly module. As the product grew and was tested on various platforms, each failure of the module was organized into clones each time a failure was fixed. This is what I call “SELF DOCUMENTING CODE”. This has nothing to do with me sitting and documenting anything. Its just that the STRUCTURE of a bug fix (or product enhancement) lives on long after the coding is done, as long as no one is foolish enough to delete the cloned sections that ‘DOCUMENT’ what happened.

In actual practice, this organizational ‘history’ is so powerful that I can’t begin to describe it. A ‘REVERSE LEARNING CURVE’ happens when an engineer gets a Leo file that already has the ‘interrupt time sensitive’ routines grouped together by the last unfortunate soul who had to work on them. There may not be any more written documentation, but the knowledge contained in the structure can be breathtaking. It is certainly timesaving. I find this particularly true in my own case. Often I’ll look at some code that seems totally unfamiliar and think ‘what idiot wrote this crap’. Then I’ll look at the version control comments and realize that I wrote the crap. Then for sure I know the documentation is non-existent, but the clones I used to develop it are still there, and they always serve to refresh my memory in an indescribable way.

Enough of this commentary, I just looked at the clock. Best wishes to anyone willing to try Leo for a week. I hope you will be glad you did.
By David McNab:

(Ed, don't read this - go away)

Does anyone know of any Open Source developer awards that accept public nominations?

I feel strongly that Ed Ream, our ever-patient, ever-productive Leo architect deserves a nomination.

Amongst other reasons, for: * Delivering the first usable visual literate programming tool. * Adding a vast abundance of new features. * Making possible a previously unimaginable amount of leverage in code editing. * Eliminating vast amounts of menial programming labour. * Tirelessly and patiently supporting users, and catering to a wide range of feature requests.

By Gil Shwartz:

I do not know of such award, but I will definitely back the nomination. Leo is unimaginably useful and I always find new things it already knows(!) how to do. Indeed I am amazed by the never-ending resources and patience Edward is putting into it and its users community. Excellent.
Leo is an interactive editor for organizing text fragments hierarchically and sequentially into one or more files and hierarchical folders, without arbitrary limits on the number and size of text fragments and the depth of the hierarchy.

Tangle is a tool for combining hierarchically and sequentially organized text fragments into text files, hierarchically grouped into folders, with hierarchical or sequential organization of text within the files, and without arbitrary limits on the size and number of files and the depth of the hierarchy of folders and text nesting within the files.
Leo is EXACTLY the kind of outliner I was looking for--fantastic job!
I am a huge fan of Leo. I think it's quite possibly the most revolutionary programming tool I have ever used and it (along with the Python language) has utterly changed my view of programming (indeed of writing) forever.
Just a quick note to thank you for your work on Leo. It is a really nice piece of work!

It makes "literate programming" much easier. [I've been playing around from time to time, but it was tedious, so I continued with my usual way of working.]

Again, this is a great tool!
I want first to thank you for Leo. This is a very useful and interesting tool.
Thank you very much for leo. I think my way of working with data will change forever.

I have just tried leo for 2-3 days, yet I am certain this will be a revolution. The revolution is as important as the change from sequencial linear organization of a book into a web-like hyperlinked pages.

The main concept that impress me is that the source listing isn't the main focus any more. You focus on the "non-linear", hierarchical, collapsible outline of the source code.
Leo is amazingly stable. Docs are often weak with Open Source Software. Not so Leo: Leo is unusually well documented.

I gave Leo a try a while ago, but did not recognize its strengths as an outliner. In April 2003 a gave it another try. And when I saw in the online help mentiontioning clones almost in passing (you got to emphasis this much more!), I knew: This is it.

If you are like me, you have a kind of knowledge base with infos gathered over time. And you have projects, where you use some of those infos. Now, with conventional outliners you begin to double these infos, because you want to have the infos needed for the project with your project. With Leo you can do this too, but if you change text in one place IT IS UPDATED IN THE OTHER PLACE TOO! This is a feature I did not see with any other outliner (and I tried a few). Amazing! Leo directly supports the way I work!

And when I saw, how easily I can script it with one of my favorite programming languages (i.e. Python), I fell in love with Leo. Sounds silly, perhaps. But with a script written and tested in about 20 minutes I extract time expenses out of my diaries as easily as it can be. Yet I did not discover all of Leo, but if there's anything Leo cannot do yet, I'm pretty sure that I can script it.

Not yet convinced? Then think of this final but VERY IMPORTANT point: With Leo YOUR DATA ARE YOURS! Leo is Open Source, Leo is written in Python, thus Leo is platform-independent, Leo stores your infos in ASCII format (XML). This means, that you are not locked into a company if you use Leo. YOUR DATA ARE YOURS! Think of this if you again are working with your favorite PIM.

Edward, keep up this extraordinary good work!

fgeiger@datec.at
I have been playing around with Leo and am really very impressed! It should either replace or greatly augment the development tools that I use."
I must say I am very impressed. I have been absolutely seduced by Leo over the past few days. I tell you, I can not put it down. I feel like a kid with a shiny new bike. Hopefully I can get you guys some code one of these days. I'm already bursting with new ways I'd like to use the tool in the future.
I've tried Literate Programming tools off and on for more than 10 years, mainly because the promise was so great. I've abandoned them every time because working with the various CWeb-like tools was so awkward.

Leo changes all that. The most important benefits promised by Literate Programming are realized by using Leo, without the awkwardness of the other tools.
When first I opened Leo, it was out of curiosity. But having used it...

I'LL NEVER GO BACK. THEY'LL HAVE TO PRY LEO OUT OF MY COLD, DEAD FINGERS!

Seriously, it should be renamed "Crack Cocaine" because it's that addictive. I'm ready to start a 12-Step group.
I wanted to thank you for the effort you've put into Leo. It looks fantastic. I've always though that Literate Programming was a good idea, but the tools were completely un-workable.
I was turned on too a very cool tool written in Python. Leo is a marrage of outlining and literate programming. Pure genious. The main reason I am impressed with this tool is that it doesn't affect your choice of tools. You can use whatever IDE for whatever language and switch back and forth between leo and it.
And yes, I've been using Leo a few hours every day for more than a week, now. :^) Actually, it's become my main development platform, and I do this for a living.
<tomlee@bigpond.net.au>

I came across Leo the other day while reading through Dr. Dobbs' Journal, and after all of five minutes I must say I'm absolutely astounded by the power of such a simple idea! It works great and I can immediately see the benefits of using Leo in place of the standard flat file editor.

I know this is short and sweet, but thanks very much for such an interesting piece of software.
Cloning is pure genius! I'm studying for the Chartered Financial Analyst exams, and I use Leo to take notes on the voluminous course material. Along the way, I'm developing little utility functions in the array programming language J (http://www.jsoftware.com) for working the examples and the exercises in the textbooks. J is vanishingly terse, so the literate programming facilities in Leo aid comprehension of code immensely. Even more useful than the traditional lit prog for my purpose, though, is Leo's cloning facility, which allows me to create several views on the CFA course material. My main view follows the prescribed study guide. Another view is organized like the textbooks. Yet another gives me a glossary of terms. And when I'm done, I'll have some nice libraries of J functions that I can re-use later in other projects. Also, a little exporting from Leo and post-processing with noweave should give me some printable docs.

Thanks for hunting down these bugs, and thanks for producing such a cool tool. Wanting to customize and script Leo has me itching to dust the Python books off this weekend.
I just discovred Leo today. I am one of the many droves of people that came stampeeding by due to you being Slashdotted. I will say that I am extemely impressed at how stable and useful Leo appears to be. As soon as I ran though the slide presentations of how Leo works, it struck me that I have always been looking for this in an editor.

I am very impressed that you developed this app single handedly. It looks like a very impressive piece of work.
Hey, Edward, please bear with us. I know you're overwhelmed, but you've come up with perhaps the most powerful new concept in code manipulation since VI and Emacs.

I'm blown away with how Leo can slice'n'dice C and Python files into their component parts.

Leo absolutely ROCKS! :) It's exactly the tool I've been wanting for years but have never got around to writing.

One thing I really like is the ability to create functions in any order I please, and not think about what files to put them into till later. This ability alone has freed up a lot of my thought processes with coding.

---

For me, I'm so grateful I saw the Leo write-up on Slashdot, otherwise I would probably never have come into contact with it.

A funny observation with Leo is that when I 'Leo-ise' other people's code, Leo makes the code's structure so transparent that design faults become very quickly apparent - eg maintenance pain caused by lack of factorisation.

Thanks again for a wonderful (I won't say 'editor' because the word doesn't do Leo justice) code structuring system.
We who use Leo know that it is a breakthrough tool and a whole new way of writing code.

Leo is a powerful tool for organizing text into tree structures, and for just generally attacking a number of problems from a tree-based perspective.

<opinion> There has been a lot of hype about XML and all the uses to which it can be put. But, to my mind, the greatest benefit of XML is that it allows people to easily work with tree structures. All the benefits of XML really flow from that. Leo is the best application I've seen for conceiving and planning the trees that are then put into action via various XML technologies. </opinion>"

Outlining Editors have existed for a while (e.g. PC-Outline, MORE) but Leo takes a major leap beyond previous tools for three reasons:

1. You can embed a simple macro language inside of a Leo document to instruct Leo to assemble pieces of the Leo document into another file. This facilitates, among other things, one type of Literate Programming.

2. Leo saves its files in XML format. This means that you can easily import and export Leo documents to any number of programs that can understand XML. For example, you can use an XSLT stylesheet to export a Leo file to HTML. And you can import any XML file into Leo using another XSLT stylesheet.

3. Leo is fully scriptable via Python."
The Word outlines are very useful. But Leo makes Word look like a clunky toy.

#1 Reason would probably be clone nodes. One node can point to another. Another way of putting this is is that a leaf can be on more than one tree. For example, suppose you have a list of recipes. You simultaneously put a single recipe under multiple categories or even multiple hierarchies. You could put "3 bean enchilada" simultaneously under Recipes-Mexican and Food-Gas. Another example would be, if you are a biologist trying to decide under which genus to put a new species, you could put the species under two simultaneously. In effect, you can build a 3-D tree. For a further illustration see http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm

#2 Reason would probably be that Leo outlines can be embedded in external text files. So, a Leo outline is more than an outline, it is a meta-structure that can be added to another text without changing that text, but rather providing an external roadmap to the text. Microsoft Word has a text (xml) version with a commenting convention, so Leo can even be used to add outlines into Word docs, altho it isn't set up to do that now. For example, see http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm In this case, the upper window of Leo is the metastructure, and the bottom window is the file to which the metastructure is being applied, viewed one node at a time.

I may not have made #2 very clear, but it is actually a very useful feature. It takes some getting used to before one sees all of the possibilities tho. One way to think of it is that Leo allows you to throw external documents into your outline, and yet the external document remains independent and can still be edited separately.

Some other cool things about Leo which Word doesn't feature: 1. Pure xml output that is easy to transform into other formats (next version of Word will have true XML format, but not as easy to work with). One consequence of this is that Leo files can be transformed pretty easily to web pages with their outlining capability intact. 2. Easy to add features since is programmed in Tkl and open source. Maybe your average user can't start hacking on it, but a surprising amount can be tacked on by flipping thru the Tkl manual. 3. Free, opensource, multiplatform 4. Leo is scriptable with Python. It should be possible to build a Tickler into Leo using Python scripting, for example.
@nocolor

I only have one week of Leo experience but I already know it will be my default IDE/project manager. I have seen several old threads on c.l.python where people complain about the lack of a project manager for the free/standard Python IDE's like Idle. Leo clearly solves that problem and in a way that commercial tools can't touch. I suggest we reply to future such postings with that gentle nudge.
Hi, I have been interested in creation of a heirarchal text-editor for a while and I just came across Leo, which I found to be almost exactly what I had in mind, which is fantastic because I don't have to code anything!
I am an amateur photographer. I use plain old 35mm. film for my pictures. Over the weekend, I used LEO to organize my lists of pictures. It is quite helpful -- I can have separate nodes for pictures I have enlarged, as well as pictures I have submitted to our local camera club. Thanks!
Just as structured programming reveals and disciplines the flow control of a program, [Leo] allows the designer to reveal and discipline structure at many layers simultaneously: data structures, object structure, entity-relationship structure, client-server structure, design pattern structure, temporal structure, project management structure, and any other structure relevant to the system.
LEO reminds me a great deal of things I loved when I used Userland's Frontier (an outlining CMS with a native OODB) - but Frontier wasn't hackable enough for me, and it wasn't oriented towards coding and literate programming, and you couldn't round-trip rendered pages (big LEO win). This is really a super tool - in a matter of days I've started to use it on all my projects and I still haven't figured out how I lived without it.

Thanks again.
Leo is a very good concept and is a revolutionary step in the right direction for programming. I appreciate your work.
Leo files are so wonderful at organizing information, it'd be powerful if we could share them on the web with minimal effort.

A Leo file is an ideal documentation tool, collecting the assorted readme.txt files, the comments from the source files...as well as the config files themselves.
I have been using Leo for about 3 weeks and I hardly use my other programming editor anymore...I find it easy and enjoyable to use. I plan to adopt it as my presentation tool for code reviews.
First of all, kudos to you for the excellent progress you've been making with Leo. I upgraded today after about three months of using and older version and I was thrilled to see all the great improvements that have happened so fast. I especially love the ability to go to next clone. I think you're really showing what open source can do and your current trajectory puts you on track to kick Emacs into the dustbin of computing history.

So today I copied all my data (personal information manager and project management stuff) out of my old outliner (ThoughtManager, which synchs with and runs on the Palm) and put it into Leo. It took me hours to do it and then to rearrange it the way I really wanted it. But having the ability to make clones and have different ways to view my data is, as you know, fabulous. In my case, for personal information and project management things, I used the flexibility of clones to allow me to see my data in several different views: 1) by project, the logical hierarchical breakdown by topic, 2) by person, so whenever I'm talking to someone I can easily see all the pending items related to them which may be spread over multiple projects, 3) by priority, so I can see what needs to get done sooner and what can wait for later and, 4) a special case of priority called "Today" for the things I'm going to focus on in the coming hours.

Now here's why I don't miss the ability of my old outliner to synch the entire outline with the Palm. It turns out the main thing I really want in the Palm is the top category "Today" so all I have to do is have Leo flatten that one heading into a text file (and it kindly remembers the name and directory of the file I used last time) and then I'm done because I've told the Palm Hotsync manager that that file should be sent to Palm memo pad every time I synch. The Palm Hotsync manager does a nice job of sending a text file to the Palm memo pad and even breaks the file up into multiple memo records if it's too big to fit in just one. So that gives me enough to be able to browse (or full text search) the small amount of data that I really want right inside my Palm (which is also my cell phone). Quick and dirty but it works.

For times when I want my whole outline with me, Leo wins again because thanks to its cross platform nature I can take my whole outline with me on my Mac iBook, even though I usually edit it on a Windows PC (which is the only kind of machine my old outliner would run on). Quite frankly, although my old outliner was able to shoehorn the whole thing into my palm/cellphone, it was a pain to access it on the small screen and slow processor. Now when I anticipate I'll need the whole thing, for example when I'm going to a meeting, I can put it on my Mac iBook (under X and Fink for now until Python can do it native under Aqua) and have real, full access to it all.

I think now in addition to being great for programming Leo is also a great PIM. Being able to flatten a strategically chosen portion of the outline into a known file name that the Palm synch manager has been told to send to the Palm on every synch does the trick for me. I wonder if you would consider something like an @flatten directive so I can have that done automatically for me every time I save my outline? For now it's up to me to flatten the node I want manually, although once I've done that the transfer to the Palm is automatic.

You're my hero! Thank you so much.

-- Dan

-----

Another day, another breakthrough using Leo -- now I realize Leo is the best URL bookmark manager there is. No more bookmarks menus or favorites lists inside the browser for me. With the @url directive I can just double click on the URL to open it in my browser. Leo lets me arrange the URLs in a hierarchy (or multiple hierarchies), attach notes to them, save clippings of things I read on the sites. It's sooo much better than anything the browsers have built in and it lets me easily use different browsers on different platforms and different machines (try that with the browsers' built-in bookmark managers).

When using Leo as a project manager and personal information manager as I do I can heavily annotate every task and project with helpful and relevant URLs. And since URLs can be of the file:// form, they're not just for web pages or HTML documents; I can link to any file on my disk of any type to be opened by any program.

Leo is a quantum leap for me in terms of how many projects I can manage and how much information I can find and organize and store in a useful way. I'm a data-mining army of one now and the web is my playground. Every time I find a web page that has interesting links to others, those links get stored in my Leo outline too, right where I can find them and make practical use of them. I can easily accept dozens of valuable links every day and integrate them into what I'm doing in a way that I'm confidant they won't get lost or forgotten. Before I always used to get bogged down by the difficulty of managing bookmarks inside the browser. But now I'm no longer the victim of information overload buried in the knowledge landslide of the Internet; instead I'm the professional strip miner with the world's biggest bulldozer. I eagerly plunge into mountains of data and emerge with all the valuable information nuggets neatly stored and organized. And my storehouse of knowledge is a flexible thing where I can reorganize and prioritize and massage the data to my heart's content as I learn more about it and decide to use it in different ways for different purposes. It's the difference between the pick axe and the steam shovel for me.
I found this blog entry by someone (a talented former coworker of mine actually) complaining about some poorly written code she had to maintain:

http://www.ceejbot.com/blog/space/2003-01-29#the_news_at_10

She said: "You'd need a bulldozer to start refactoring it. " That was my cue to write a long message explaining that there is indeed such a bulldozer and it's called Leo. (You can see my message there as a reply to her original posting.) I gave her my recipe for how to get someone else's messy, scary code into Leo and how to break it down into manageable chunks.
@nocolor
Please be aware of the following problems with leo:

1. leo.py v3.0 and Borland v2.6 create derived files that can not be read by older versions of Leo! leo.py v2.0 and Borland v2.6 can read all previous derived files.

2. Leo will report read errors when opening a .leo file that is not "in synch" with derived files. This can happen when opening a backup copy of a .leo file. When such errors happen you have two choice:

Choice 1: Use the "Read Outline Only" command to recover the outline using only the .leo file. This is the recommend way because it preserves all clone links.

Choice 2: Use one or move "Read @file Nodes" commands to update the outline from derived files. This way is will break any clone links from the outline into the
This node duplicates the information in Chapter 2: Installing Leo.
leo.py is Leo2 written in Python and Tk. It should work on any platform that supports Python 2.2 or later and Tk 8.3 or later.

Download Python from http://python.org/ Download tcl/Tk from http://tcl.activestate.com/software/tcltk/

The section called "How to Install Leo on MacOS X" contains special instructions for the Mac. Leo will not run on MacOs 9.
@nocolor

leo.py requires Python 2.1 and tcl/tk 8.3 or above.

Download Python from http://python.org/ Download tcl/Tk from http://tcl.activestate.com/software/tcltk/

Warning: When building Tcl on Linux, do not specify "--enable-threads" .. only use Tcl with the default "threads not enabled" case.

Leo is distributed as a single compressed folder. Simply uncompress this folder and run leo.py. You can run leo.py by double-clicking leo.py. You can also run leo.py from a Python interpreter as follows:

@color

import leo leo.run() # runs leo.py, opening a new outline. leo.go() # runs leo.py after reloading all modules. leo.leoOpen(fileName) # runs leo.py, reloads all modules, and opens fileName.

@nocolor

On Linux, the following shell script will allow you to open foo.leo files by typing leo foo

#!/bin/sh python <leopath>/leo.py $1

where <leopath> is replaced with the path to the leo directory.
@nocolor

leo.py requires Python 2.1 and tcl/tk 8.3 or above.

Download Python from http://python.org/ Download tcl/Tk from http://tcl.activestate.com/software/tcltk/

Leo is distributed as a single compressed folder. Simply uncompress this folder and run leo.py. You can run leo.py by double-clicking leo.py. You can also run leo.py from a Python interpreter as follows:

@color

import leo leo.run() # runs leo.py, opening a new outline. leo.go() # runs leo.py after reloading all modules. leo.leoOpen(fileName) # runs leo.py, reloads all modules, and opens fileName.

@nocolor

Another way to open files would be to create a batch file. On Windows, I put the following .bat file in c:\Windows

cd c:\prog\LeoCVS\leo c:\python22\python c:\prog\LeoCVS\leo\leo.py %1

This opens the file specified by the first argument (%1).
The following nodes discuss installing leo.py on MaxOS X Jaguar.

Apparently the process is getting easier and easier.
Leo on MaxOS Jaguar

Fortunately, this has gotten much easier this year (2003). I did it recently and here's what I remember.

You will need:

the latest Leo2.py (I used Leo 3.10 (ignore "leo(Macintosh)") http://prdownloads.sourceforge.net/leo/leo-3.10.zip?download

the latest TclTkAqua (I used 8.4.1) http://sourceforge.net/project/showfiles.php?group_id=10894&release_id=117868

the latest X11 beta from Apple (I used Public Beta v0.2.1) http://www.apple.com/downloads/macosx/apple/x11formacosx.html

Install X11 to get an OS X native X11 window server, the TclTkAqua to get the Tcl and Tk toolkits (which run on X11) to support Leo's GUI, and install Leo.

Once these are installed, treat leo as a typical UNIX installation running with an X11 server (unrelated to OS X). leo never needs to know that it's running on OS X (thanks to Apple releasing their aqua native X11 server!).

To run it, make sure to run the X11 server first. Then run leo from the command line (as usual). If the X11 server isn't running, leo won't have a way to display windows or GUI.

Good luck!
Bruce M. Bolden, bruceb@cs.uidaho.edu, is using Leo on MaxOS X. Here is his description of how to get Leo working there:

I installed: - Latest OS X Developer tools - latest (as of about two weeks ago) tk/tcl MacOSXTk8.4a4-2.tar.gz - the machopython distribution http://prdownloads.sourceforge.net/wxpython/MachoPython-2.2.1.dmg

For OS X 10.1, I followed the directions at: http://tony.lownds.com/macosx/tkinter.html

That worked fine for 10.1, but didn't work for 10.2. It took awhile to find a Python distribution that worked correctly with tk/tcl.

I was just getting into Leo about a week before 10.2 was released and thought about---what if it breaks.... 10.2 broke it, but I was able to find a solution as described above.
I just finished building tk successfully! The magic step was the execution of the command

cp ../generic/prolog.ps to ../library/prolog.ps

AFTER

cd ../../tk/macosx

in the instructions on "Steps to build Tcl/Tk 8.4.0 on MacOS X":

http://mini.net/tcl/4052

I found this solution while looking at the tcl-mac mailing list archive. The earlier (current?) tar ball on SourceForge did not have the file prolog.ps in the library directory:

http://sourceforge.net/mailarchive/forum.php?thread_id=1060496&forum_id=3853

Copy and Paste seem to work just fine now in Leo! This was my original question for you.

I also noticed that someone else built everything on OS X 10.2 (Jaguar), but they did it under X. Since, the new version of Tk appears to support the OS X interface quite well, I'm not going to bother installing X (for now).

So, Leo works two different ways on OS X 10.2.
Installing Leo under MacOS X 10.2 =================================

MacOS X 10.2 comes with a built-in python interpreter, but unfortunately the built in interpreter has no support for Tkinter (the library Leo uses to display its graphics), so if you try to run Leo using the built-in python you will get an error message saying that Tkinter cannot be imported.

This document describes the steps you must take to get Leo to run under MacOS X.

There are two ways of running Leo under Mac OS X. Leo can either be run natively in its own Aqua window or it can be run under an X11 server. Native running is usually more elegant, since it gives Leo the standard Mac OS look and feel. Running it under X11 might be a good option if you aldready use a lot of X11 programs. This document only describes the installation procedure for Aqua.

See the children of this node for details.
1. Install the TclTkAqua package.

Download and install the file ``TclTkAqua-8.4.1-Jaguar.dmg`` from http://sourceforge.net/project/showfiles.php?group_id=10894&release_id=117868.

Note that you probably do not want to install the 8.4.2 version. The TclTk project uses odd numbers for stable versions and even numbers for unstable development releases.

There is also a *BI* version of TclTkAqua. *BI* stands for *Batteries Included*. This distrubiton includes many of the commonly used extensions to Tk, in addition to Tk itself. It is not needed for running Leo.

2. Install the Python interpreter

A python interpreter compiled to run with AquaTk is available at https://sourceforge.net/project/showfiles.php?group_id=10718. Download the file::

MacPython-OSX-2.3a2-2.dmg?download

or any newer version of the interpreter and install it.

At least some versions of this installer has a bug that appears when you try to install it on a system with a single disk. The installer will erroneously report that the program cannot be installed on the disk. To fix the problem just *Go Back* and press *Continue* again. You will then be able to continue the installation.

3. Download and install the source code version of Leo from Leo's project page.

4. Rename the ``leo.py`` file to ``leo.pyw``.

This tells MacPython that ``leo.pyw`` is a GUI script that should run in its own GUI window.

You should now be able to run Leo by double clicking on the ``leo.pyw`` file. You can create an alias of the file in some convenient location to have easy access to the program.

To run Leo from the command line, type:

/usr/local/bin/pythonw leo.pyw

This should start Leo. (``pythonw`` is a small wrapper script that is needed to work around a bug in AquaTk which prevents windows from being brought to front).
To get .leo documents to open with Leo, you must first create an AppleScript that can launch Leo. Start up the script editor and enter the following text::

on open filelist repeat with i in filelist do shell script "/usr/local/bin/pythonw /Src/Leo/leo.pyw " & POSIX path of i & " > /dev/null 2>&1 &" end repeat end open

on run do shell script "/usr/local/bin/pythonw /Src/Leo/leo.pyw > /dev/null 2>&1 &" end run

You should replace ``/Src/Leo/leo.pyw`` with the path to wherever you have installed Leo.

Save this script as *Application* at some suitable location. Name it "Leo" or something similar. You can now launch Leo by double-clicking the script icon. You can also open Leo files by dropping them on the script icon.

Double click any ``.leo`` file. In the dialog box that appears select *Choose Application...* and choose the script application you just created.
The Icons folder contains two Icons, called LeoApp and LeoDoc, that represent the Leo application and Leo documents respectively.

In Windows, to associate LeoDoc with .leo files, open the Setting:Folder Options:File Type panel, create an entry for .leo files. Then choose Change Icon and select the LeoDoc icon. Associate leo.py as the application to open .leo files.
leo.py 2.4 and above will open .leo files automatically provided that you associate leo.py with .leo files. Here is how to open leo.py when double-clicking a .leo file on Windows 2K or XP. I'm not sure it will work for Windows 95/98/Me.

In Windows 2K or XP, Go to Start->Settings->Control panel, open the "Folder Options" applet. Select the "file types" tab from the Folder Options window. Press the "new" button. Enter LEO into the "Create New Extension" File Extension text field. Press OK.

With LEO still highlighted in the Registered File Types list box, press the Advanced button. Change the default file type field to something like "Leo Literate Outline". Press the Icon button and browse to the LeoDoc icon in your [leo install dir]\leo-2.3\Icons folder. Click OK. This gets the icons right and registers the description of the .leo file.

You now have to tell windows what to do to open the file properly. Press the new button to open the "New Action" window. In the Action field type Open. In the "Action used to perform action" type _one_ of the following lines:

[python install dir]\pythonw.exe [leo install dir]\leo.py %1 [python install dir]\python.exe -i [leo install dir]\leo.py %1

The first line opens Leo files with no console window. The second line opens Leo files with a console window and leaves the window open after Leo exits.

You should now be able to double click on a leo file in explorer with leo.py.
Leo will now draw the LeoDoc.ico from the Icons directory in Leo windows, provided you have installed Fredrik Lundh's PIL and tkIcon packages.

Download PIL from http://www.pythonware.com/downloads/index.htm#pil Download tkIcon from http://www.effbot.org/downloads/#tkIcon

Many thanks to Jonathan M. Gilligan for suggesting this code.
@nocolor

Quick start

This is the minimum you need to use Leo effectively.

1. Write body text using the noweb languages. See Using noweb, CWEB and Directives.

2. Create source files (derived files) using the Tangle command. Only @root directives under the currently selected headline are tangled by the Tangle command. Warning: The Tangle command will overwrite files specified in @root directives if they exist.

3. Use the Untangle command to propagate changes made to derived files back into the outline Untangle is useful only for making small syntactic changes. Don't attempt to change the structure of an outline by modifying derived files; it won't work.

4. Many command key abbreviations are specified in the Syntax Coloring panel.

See the Appendices for a concise summary of information about Leo.
leo.py 3.11 April 27, 2003

leo.py 3.11 is now available at: http://sourceforge.net/projects/leo/ leo.py requires Python 2.1 or above and tcl/tk 8.3 or above. leo.py will work on Linux, Windows and MacOs X Jaguar.

This version of Leo contains many important improvements since 3.10, especially support for unicode and a new plugin architecture.

The Highlights of 3.11:

** Leo supports unicode. - New @encoding directives specifies encoding for derived files. - New settings specify default encodings for .leo and derived files. - Derived files specify encodings used to create them. ** A new plugin architecture for customizing Leo. (A major improvement.) - New commands: - Toggle Angle Brackets. - Insert Time/Date (in headline or body text). - Go To First/Last Node, Parent, Next/Previous Sibling. - Write Missing @file Nodes. - Expand/Contract Node. - Improved commands: - Fixed a huge performance bug that slowed moving nodes left. - Expand/Contract commands now operate on the selected tree. ( A major improvement) - Sort Siblings command can now sort top-level nodes. - Leo now allows multiple writes of cloned nodes. (Useful when different @path directives are in effect.) - Other improvements: - New @lineending directive specifies line endings. - Improved syntax coloring for html and xml. - Leo scrolls partially visible headlines into full view. - Added "Replace tabs with spaces" checkbox in Prefs panel. - New settings in leoConfig.leo/.txt. - Added "tk_encoding" setting and related logic. - Colors for Show Invisibibles command and body_insertion_cursor - Settings affecting when control-drags start. - Defaults for unicode (see above). - Many other bug fixes and improvements.

See the children of this node for full details.
This ensures that the outline is redrawn properly after the Read @file Nodes command.
Browsing the comp.lang.python yesturday I found that there are serious problems with Python's getdefaultlocale() routine. Martin v. Löwis says "getdefaultlocale() is broken, and cannot be fixed", though he does suggest the following workaround:

leoGlobals.py uses locale.getpreferredencoding to define getpreferredencoding if locale.getpreferredencoding exists (it is new in Python 2.3alpha). Otherwise, Leo uses the code copied from the 2.3a2 version of Python to define getpreferredencoding. Either way, leoGlobals.py now defines getpreferredencoding and Leo uses getpreferredencoding rather than getdefaultlocale.
After opening the Python window the call to tkFont() threw a (caught) exception on every call to frame.setTabWidth. The fix was to add the optional root param.
skip_long was throwing an exception if there were no valid digits following a + or - sign.
- tangle.scanAllDirectives was not setting self.encoding when handling @encoding. - tangle.scanAllDirectives wasn't scanning the @encoding line properly. - fixed crashers in Tangle and Untangle logic.
Fixed bug in handleLinesFollowingSentinel: c:\prog\LeoCVS\leo\leoAtFile.py:482: No global (i) found Removed i arg from rfind.

Fixed bug in definition of BadLeoFile: c:\prog\LeoCVS\leo\leoFileCommands.py:27: Base class (exceptions.Exception) __init__() not called Added call to Exception.__init__(self,message)

Fixed bug in askYesNoCancel c:\prog\LeoCVS\leo\leoDialog.py:283: Variable (no) used before being set. Disabled defaultButton logic. New code would have to be thoroughly tested.

Removing unused routine: setDefaultLoc. c:\prog\LeoCVS\leo\leoNodes.py:2581: No class attribute (defaultLoc) found

Fixed bug in put: changed a to app() c:\prog\leoCvs\leo\leoFrame.py:4973: No global (a) found
I had completely misunderstood __del__ methods! __del__ methods _disable_ the gc's ability to collect circular garbage (!!) and so __del__ methods will _never_ be called (!!)

BTW, when deleting an entire Leo window Leo calls various destroy methods to unlink items. This allows the garbage collector to collect all data structures. Because of unlimited undo only these destroy methods have the potential for creating "real" garbage.
This adds http:\\ to url's if needed.
New in version 3.11. The @lineending directive allows you to specify the line endings for individual derived files. This directive will override the output_newline setting in leoConfig.txt. Note that @lineending _never_ affects the line endings in .leo files themselves: we must have consistent line endings in .leo files! See below for more details.

The valid forms of the new @lineending directive are:

@lineending nl (the default, Linux) @lineending cr (Mac) @lineending crlf (Windows) @lineending lf (same as nl, not recommended) @lineending platform (same as platform value for output_newline setting in leoConfig.txt)
This will have no effect until version 4.0.
Leo now uses unicode internally for most strings. This solves a lot of problems. The children of this node discuss all the details...
This fixes several bugs.
In particular, the locale returned by locale.getdefaultlocale()[1] need not be a valid unicode encoding.
Leo now uses unicode internally for most strings. The highlights:

1. Leo converts headline and body text to unicode when reading .leo files and derived files. Both .leo files and derived files may specify their encoding, with "utf-8" being used by default. If the encoding used in a derived file is not "utf-8" it is represented in the @+leo sentinel line. For example:

#@+leo-encoding=iso-8859-1.

The "utf-8" encoding is a "lossless" encoding (it can represent all unicode code points), so encoding and decoding to and from utf-8 plain strings will never cause a problem. When reading or writing a character not in a "lossy" encoding, Leo converts such characters to '?' and issues a warning.

2. When writing .leo files and derived files Leo uses the same encoding used to read the file, again with "utf-8" used as a default.

3. leoConfig.py contains two settings used to specify the default encoding used for .leo and derived files:

default_derived_file_encoding = UTF-8 new_leo_file_encoding = UTF-8

I do not recommend changing new_leo_file_encoding (see the comments in leoConfig.leo) and you may set default_derived_file_encoding to anything that makes sense for you.

4. You may use the @encoding directive to specify the encoding used in a derived file. You can't mix encodings in a single derived file.

This is all should need to know to use extended character sets on Leo.
leoConfig.py now contains two new settings, with the defaults shown below:

default_derived_file_encoding = UTF-8 new_leo_file_encoding = UTF-8

These control the default encodings used when writing derived files and .leo files.

I do not recommend changing new_leo_file_encoding (see the comments in leoConfig.leo)

You may set default_derived_file_encoding to anything that makes sense for you.
The following paragraphs discuss code-level details. You should not need to read this section in order to use Leo.

1. Leo uses unicode objects in vnodes and tnodes to denote headline and body text. Note that unicode strings have no encoding; only plain strings have encodings. This means that once an (encoded) plain string has been converted to a unicode string it doesn't matter how the unicode string was created. This is the key that makes Leo's new code robust.

So internally Leo never has to worry about encodings. The only times encoding matter is when encoded strings are converted to and from unicode when Leo reads or writes files (or when the passing strings to and from Tk.Text widgets, but the idea is the same).

2. Python expressions that mix unicode strings u and plain strings s, like:

u + s or u == s or u[5] == s[2:]

are promoted to unicode objects using the "system encoding". This encoding should never be changed, but we can't assume that we know what it is, so for safety we should assume the most restrictive encoding, namely "ascii". With this assumption, Leo's code can't throw an exception during these promotions provided that:

- All strings are converted to unicode when Leo reads files or gets text from Tk.Text widgets. - All string literals in Leo's code have only ascii characters.
It is now much easier to customize Leo. The new scheme is based on plugins (files in the plugins directory) rather than modifying customizeLeo.py. Many thanks to Paul Paterson for suggesting this scheme and for doing the first implementation.
@nocolor

New in 3.11. Leo now looks in the plugins folder for files whose name matches mod_*.py. Leo tries to import all such files at startup ("start2") time.

The child of this node gives an example of the code that creates the Open With menu. The following code, appearing at the end of mod_open_with.py, registers two different hook functions. The on_idle function is the "idle" hook. The create_open_with_menu is the "start2","open2" and "command2" hooks. @color

if 1: # Register the handlers... registerHandler("idle", on_idle) registerHandler(("start2","open2","command2"), create_open_with_menu) es("...open with")

@nocolor Plugins may call registerHandler with a hook name like "idle" or a list of hook names like ("start2","open2","command2"). Also, you can use the "all" hook name to indicate that code will be called for all hooks. This is useful for tracing hooks. Plugins may also call registerExclusiveHandler for hooks that should not be redefined in other plugins.

mod_open_with.py also contains the on_idle and create_open_with functions that actually handle the hook. Such functions have two arguments: tag and keywords. You will find many examples of this in LeoPy.leo.

About distribution:

1. LeoPy.leo now contains all the plugins in @ignore'd @file nodes. This way Leo won't complain when you open LeoPy.leo.

2. Most plugins actually are disabled: the code that registers the hooks is contained in #if 0: blocks. So to create a pluging you have to remove the @ignore and you have to change #if 0: to #if 1.

3. Most mod_*.py will never be part of Leo distributions, and the single-click installer will create an plugins folder containing just mod_open_with.py.

4. There is no longer a need for a customizeLeo.py file.

About error checking:

At present Leo will disable all hooks if any hook throws an exception. In practice this isn't a big deal, but obviously something better could be done. I won't do anything until after 3.11b1 goes out.

How to send me your plugins:

It should now be _much_ easier to submit a plugin for inclusion in LeoPy.leo. Just send me a .leo file containing @file mod_your_plugin.py. Please have this @file node be @ignored: this is more convenient for me. Ideally, your .leo file should contain _only_ your plugin code.
@color @language python @ignore

"""Open With handler"""

from customizeLeo import * from leoGlobals import *

@others

if 1: # Register the handlers... registerHandler("idle", on_idle) registerHandler(("start2","open2","command2"), create_open_with_menu) es("...open with")
# frame.OnOpenWith creates the dict as follows: # dict = {"c":c, "v":v, "f":f, "path":path, "time":time}

def on_idle (tag,keywords):

import os a = app() for dict in a.openWithFiles: path = dict.get("path") c = dict.get("c") v = dict.get("v") if path and os.path.exists(path): try: time = os.path.getmtime(path) if time and time != dict.get("time"): dict["time"] = time << update v's body text >> except: pass
<< set s to the new text >> body = v.bodyString() << set conflict flag >>

if conflict: # Report the conflict & set update. import leoDialog d = leoDialog.leoDialog() message = "Conflict in %s.\n\n" % (v.headString()) message += "Replace outline with external changes?" update = d.askYesNo("Conflict!",message) == "yes" else: update = s != body

if update: h = v.headString() es("changed:" + h) v.setBodyStringOrPane(s) c.selectVnode(v) v.OpenWithOldBody = s
try: # Update v from the changed temp file. f=open(path) s=f.read() f.close() except: es("can not open " + shortFileName(path)) break
try: # The OpenWithOldBody attribute does not normally exist in vnodes. old_body = v.OpenWithOldBody conflict = body != old_body and body != s except: conflict = v.isDirty() and body != s
def create_open_with_menu (tag,keywords):

if (tag in ("start2","open2") or (tag=="command2" and keywords.get("label")=="new")):

<< create the Open With menu >>

# Enable the idle-time hook so we can check temp files created by Open With. from leoGlobals import enableIdleTimeHook enableIdleTimeHook(idleTimeDelay=500)
@ Entries in the following table are the tuple (commandName,shortcut,data). data is the tuple (command,arg,ext). command is one of "os.system", "os.startfile", "os.spawnl", "os.spawnv" or "exec". 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 extension ext, Otherwise, Leo computes an extension based on what @language directive is in effect. @c

idle_arg = "c:/python22/tools/idle/idle.py -e "

if 1: # Default table. table = ( ("&Idle", "Alt+Shift+I",("os.system",idle_arg,".py")), ("&Word", "Alt+Shift+W",("os.startfile",None,".doc")), ("Word&Pad","Alt+Shift+T",("os.startfile",None,".txt"))) else: # David McNab's table. table = ( ("X&Emacs", "Ctrl+E", ("os.spawnl","/usr/bin/gnuclient", None)))

top().frame.createOpenWithMenuFromTable(table)
Simplified and regularized hooks:

- replaced "event1" and "event2" hooks with 'regular' hooks. * Much easier to understand. - added "iconrclick" hooks. - added "bodyrclick" hooks. - added "save1" and "save2" hooks - added "dragging" hooks. * These are called when drag hook called while already dragging. - added "hyperenter" and "hyperleave" hooks. * These only fire if use_hyperlinks = 1 in leoConfig.txt. - removed "activate" and "deactivate" hooks. * These were not reliable. - almost all hooks now have c,v arguments. - "command" hooks now return "undo" and "redo" rather than "cantundo" and "cantredo"

Improved code:

- improved the tracing of hooks. - the tracing code also checks to make sure that most hooks have c,v keywords. - created es_event_exception(). - put all event handlers in try/except blocks that call es_event_exception on errors.
At startup time Leo looks all files of the form mod_*.py in the plugins directory. Leo assumes that these files are "plugins" containing Python modules that customizes Leo's operation. Leo attempts to import each file.

Each module should register routines that are called at various times during execution. Such times are identified by strings known as "tags". The code in in one or more plugins corresponding to each tag are known as the "hook" routines for that tag. Leo catches exceptions (including syntax errors) in hook routines, so it is safe to hack away on this code.

Leo passes two argument to all hook routines: the tag and a keywords dictionary containing additional information. For example, keywords["label"] indicates the kind of command for "command1" and "command2" hooks.

For some hooks, returning anything other than None "overrides" Leo's default action. Hooks have full access to all of Leo's source code. Just import the relevant file. For example, top() returns the commander for the topmost Leo window.

The following table summarizes the arguments passed to hooks: ( Overrides is "yes" if returning anything other than None overrides Leo's normal command or event processing.)

tag argument keys in keywords (hook name) overrides when called dictionary argument --------- --------- ----------- ------------------- "bodyclick1" yes before normal click in body c,v,event (new) "bodyclick2" after normal click in body c,v,event (new) "bodydclick1" yes before double click in body c,v,event (new) "bodydclick2" after double click in body c,v,event (new) "bodykey1" yes before body keystrokes c,v,ch,oldSel,undoType "bodykey2" after body keystrokes c,v,ch,oldSel,undoType "bodyrclick1" yes before right click in body c,v,event (new) "bodyrclick2" after right click in body c,v,event (new) "boxclick1" yes before click in +- box c,v,event (new) "boxclick2" after click in +- box c,v,event (new) "command1" yes before each command c,v,label (note 2) "command2" after each command c,v,label (note 2) "drag1" yes before start of drag c,v,event (new) "drag2" after start of drag c,v,event (new) "dragging1" yes before continuing to drag c,v,event (new) "dragging2" after continuing to drag c,v,event (new) "end1" start of app.quit() "enddrag1" yes before end of drag c,v,event (new) "enddrag2" after end of drag c,v,event (new) "headclick1" yes before normal click in headline c,v,event (new) "headclick2" after normal click in headline c,v,event (new) "headrclick1" yes before right click in headline c,v,event (new) "headrclick2" after right click in headline c,v,event (new) "headkey1" yes before headline keystrokes c,v,ch "headkey2" after headline keystrokes c,v,ch "hypercclick1" yes before control click in hyperlink c,v,event (new) "hypercclick2" after control click in hyperlink c,v,event (new) "hyperenter1" yes before entering hyperlink c,v,event (new) "hyperenter2" after entering hyperlink c,v,event (new) "hyperleave1" yes before leaving hyperlink c,v,event (new) "hyperleave2" after leaving hyperlink c,v,event (new) "iconclick1" yes before single click in icon box c,v,event (new) "iconclick2" after single click in icon box c,v,event (new) "iconrclick1" yes before right click in icon box c,v,event (new) "iconrclick2" after right click in icon box c,v,event (new) "icondclick1" yes before double click in icon box c,v,event (new) "icondclick2" after double click in icon box c,v,event (new) "idle" periodically (at idle time) c,v "menu1" yes before creating menus c,v (note 3) "menu2" yes before updating menus c,v "open1" yes before opening any file old_c,new_c,fileName (note 4) "open2" after opening any file old_c,new_c,fileName (note 4) "openwith1" yes before Open With command c,v,openType,arg,ext "openwith2" after Open With command c,v,openType,arg,ext "recentfiles1" yes before Recent Files command c,v,fileName,closeFlag "recentfiles2" after Recent Files command c,v,fileName,closeFlag "save1" yes before any Save command c,v,fileName "save2" after any Save command c,v,fileName "select1" yes before selecting a vnode c,new_v,new_v "select2" after selecting a vnode c,new_v,old_v "start1" no after app.finishCreate() "start2" after opening first Leo window c,v,fileName "unselect1" yes before unselecting a vnode c,new_v,old_v (new) "unselect2" after unselecting a vnode c,old_v,old_v (new) "@url1" yes before double-click @url node c,v (note 5) "@url2" after double-click @url node c,v (note 5)

Notes:

(1) "activate" and "deactivate" hooks: These have been removed because they do not work as expected.

(2) "commands" hooks: The label entry in the keywords dict contains the "canonicalized" form of the command, that is, the lowercase name of the command with all non-alphabetic characters removed.

Commands hooks now set the label for undo and redo commands "undo" and "redo" rather than "cantundo" and "cantredo".

(3) "menu1" hook: Setting app().realMenuNameDict in this hook is an easy way of translating menu names to other languages. Note: the "new" names created this way affect only the actual spelling of the menu items, they do _not_ affect how you specify shortcuts in leoConfig.txt, nor do they affect the "official" command names passed in app().commandName. For example, suppose you set app().realMenuNameDict["Open..."] = "Ouvre".

(4) "open1" and "open2" hooks: These are called with a keywords dict containing the following entries:

old_c: The commander of the previously open window. new_c: The commander of the newly opened window. fileName: The name of the file being opened.

You can use old_c.currentVnode() and new_c.currentVnode() to get the current vnode in the old and new windows.

Leo calls the "open1" and "open2" hooks only if the file is already open. Leo will also call the "open1" and "open2" hooks if: a) a file is opened using the Recent Files menu and b) the file is not already open.

(5) "@url1" and "@url2" hooks are only executed if "icondclick1" hook returns None.
See the Code:Customizing Leo:Plugins folder in LeoPy.leo for full details of all plugins.

- Added disabled support for @folder.

- Demoted up/down arrow stuff to a disabled plugin for now.
The idle-time hook now checks for dirty @file nodes that have been modified in an external editor since the last check. An alert is raised when such conflicting changes are found.

Improved sanitize_filename utility (in leoGlobals.py) so that it converts all characters used to denote filenames to underscores.
This allows output from registering hooks to appear in the log pane.
The following new plugins are now in LeoPy.leo.

- mod_script_io_to_body.py Redirect output from scripts to end of body pane.

- mod_vim.py Adds support for VIM editor.

- mod_french_fm.py Full translation of all Leo's menus into French.
Added support for the following options:

show_invisibles_space_background_color show_invisibles_tab_background_color
This option controls when the control key is considered to start a control-drag.

If look_for_control_drag_on_mouse_down = 1 the the control key must be down when dragging starts. Otherwise, the control key must be down when dragging ends.
This allows Leo to be used with a black background.
Two new settings in leoConfig.leo/.txt specify the format of the time/date strings inserted by the Insert Body Time/Date and Insert Headline Time/Date commands:

body_time_format_string headline_time_format_string

These settings are really the format string passed to time.strftime(format).

See http://www.python.org/doc/current/lib/module-time.html for a complete list of the format options. The "%m/%d/%Y %H:%M:%S" format is used by default, resulting in a time/date format such as: 1/30/2003 8:31:55

- if the headline is bracketed (named) then the << >> are removed. - if the headline is not bracketed (unnamed) then the << >> are inserted.
The Insert Body Time/Date and Insert Headline Time/Date commands are in the Edit Body and Edit Headline menus respectively. They insert the formatted time & date into the body or headline text. You must be editing a headline to be able to insert the time/date into the headline. If you aren't Leo will say: "Edit headline to append date/time"

Two new settings in leoConfig.leo/.txt specify the time/date format:

body_time_format_string headline_time_format_string

These settings are really the format string passed to time.strftime(format).

See http://www.python.org/doc/current/lib/module-time.html for a complete list of the format options. The "%m/%d/%Y %H:%M:%S" format is used by default, resulting in a time/date format such as: 1/30/2003 8:31:55
These commands select the first or last node of an outline and make that node visible.
This command selects the parent of the previously selected node and makes that node visible.
These commands select the previous or following sibling of a node and make the newly selected node visible.
This command writes any files (in the presently selected outline) that do not exist.
This allows time to be expressed as local time or gmttime.
The Expand To Level n commands now expand only the nodes in the presently selected outline. This makes these commands much more useful.

With these changes there is no longer any need for the Expand Subheads command.
This can be useful when clones are in the range of different @path directives.
1. Leo now longer warns when writing customizeLeo.py.

2. Inserted a lock that prevents customizeLeo.py from being written unless use_customize_leo_dot_py = 1 in leoConfig.txt. Leo writes a message to the log if customizeLeo.py is dirty but was not written because of the setting in leoConfig.txt.
This makes the file name easier to see in the Windows task bar.
This allows Leo to syntax color comments with @language plain. The @comment directive must follow the @langauge directive.
In other words, the Pressing the Enter key in the Find panel executes the Find Next command.
The syntax colorer now colors all element tags. See the example in the child of this node.
@language html @color @ignore <HTML> <!-- Author: Edward K. Ream, edream@tds.net --> <HEAD> <META NAME="GENERATOR" CONTENT="Microsoft FrontPage 4.0"> <TITLE> Leo's Home Page </TITLE> <META NAME="description" CONTENT="This page describes Leo. Leo adds powerful outlines to the noweb and CWEB literate programming languages."> <META NAME="keywords" CONTENT="LEO, LITERATE PROGRAMMING, OUTLINES, CWEB, NOWEB, OUTLINES, EDWARD K. REAM, DONALD E. KNUTH, SILVIO LEVY, OPEN SOFTWARE"> </HEAD> <!-- Last Modified: May 12, 2002 --> <BODY BGCOLOR="#fffbdc">

<H1 ALIGN=CENTER><a NAME="top"></a><IMG SRC="Blank.gif" width= "32" height="32" ALIGN="BOTTOM" NATURALSIZEFLAG="3"><IMG SRC="leo.gif" WIDTH="32" HEIGHT="32" ALIGN="BOTTOM" NATURALSIZEFLAG="3"><a href="leo_TOC.html#top"><IMG SRC= "arrow_rt.gif" WIDTH="32" HEIGHT="32" ALIGN="BOTTOM" NATURALSIZEFLAG="3"></a> &nbsp;</H1>

<H1 ALIGN=CENTER> Leo's Home Page</H1>

<p align="center"><a href="http://www.python.org/"><img border="0" src="PythonPowered.gif" width="110" height="44"> </a> <A HREF="http://sourceforge.net/"><IMG SRC="http://sourceforge.net/sflogo.php?group_id=3458&type=1" NATURALSIZEFLAG="0" ALT="SourceForge Logo"></A>&nbsp;&nbsp;&nbsp; <A HREF="http://sourceforge.net/project/?group_id=3458">Leo at SourceForge</A>&nbsp;&nbsp; <a href="icons.html"><img border="0" src="LeoCodeGray.gif" width="77" height="42"></a>&nbsp;&nbsp; <a href="icons.html"><img border="0" src="LeoProse.gif" width="81" height="42"></a>&nbsp;&nbsp;&nbsp;&nbsp;

<H3><A NAME="anchor127554"></A>Summary</H3>

<UL> <LI>Leo is a <i> programmer's editor</i> and a flexible <i>browser</i> for projects, programs, classes or data. Leo clarifies design, coding, debugging, testing and maintenance. <LI>Leo is an <i>outlining editor</i>. Outlines clarify the big picture while providing unlimited space for details. <LI>Leo is a <a HREF="http://www.literateprogramming.com/"><i>literate programming</i></a> tool, compatible with <A HREF="http://www.eecs.harvard.edu/~nr/noweb/">noweb</A> and <a HREF="http://www-cs-faculty.stanford.edu/~knuth/cweb.html">CWEB</a>. Leo enhances any text-based programming language, from assembly language and C to Java, Python and XML. <LI>Leo is also a <i>data organizer</i>. A single Leo outline can generate complex data spanning many different files.&nbsp; Leo has been used to manage web sites. <LI>Leo is a <i> project manager</i>. Leo provides multiple views of a project within a single outline. Leo naturally represents tasks that remain up-to-date. <LI>Leo is fully <i> scriptable</i> using <A HREF="http://www.python.org/">Python</A> and saves its files in <A HREF="http://www.w3.org/XML/">XML</A> format. <LI>Leo is <i>portable</i>.&nbsp; Leo.py is 100% pure Python and will run on any platform supporting <A HREF="http://www.python.org/">Python</A> and <a href="http://tcl.activestate.com/">Tk/tcl</a>, including Windows, Linux and MacOS X.&nbsp; Leo.exe runs on any Windows platform. <LI>Leo is <a href="http://www.opensource.org/"> <i> Open Software</i></a>, distributed under the <a href="http://www.python.org/doc/Copyright.html"> Python License</a>. </UL>

<H3>More Information and downloads</H3>

<ul> <LI>An excellent <a href="http://www.3dtree.com/ev/e/sbooks/leo/sbframetoc_ie.htm">online tutorial</a> and <A HREF="http://www.jserv.com/jk_orr/xml/leo.htm">Leo resource page</A>, both written by <a href="http://www.jserv.com/jk_orr">Joe Orr</a>. <LI>My brother's <a href="SpeedReam.html">slashdot article about Leo</a>, the best description about why Leo is special. <LI><A HREF="testimonials.html#anchor104391">What people are saying about Leo</A> <LI><A HREF="leo_TOC.html#anchor964914">Complete users guide</A> and <A HREF="intro.html#anchor887874">tutorial introduction</A> with screen shots. <li><a href="FAQ.html">FAQ</a> and <a href="http://sourceforge.net/forum/?group_id=3458">help and discussion forums</a>, preferable to <A HREF="mailto:edream@tds.net">email</A> so others may join in.</li> <li><a href="icons.html">Icons</a> for bragging about Leo.</li> </ul>

<a href="http://sourceforge.net/project/showfiles.php?group_id=3458">Download Leo</a> from <A HREF="http://sourceforge.net/project/?group_id=3458">Leo's SourceForge site</A>.

<P ALIGN=left>Leo's author is <A HREF="http://personalpages.tds.net/~edream/index.html">Edward K. Ream</A> email: <A HREF="mailto:edream@tds.net">edream@tds.net</A> voice: (608) 231-0766

<HR ALIGN=LEFT>

<p align="center">

<IMG SRC="Blank.gif" ALIGN="left" NATURALSIZEFLAG= "3" width="34" height="34"><IMG SRC="leo.gif" ALIGN="left" NATURALSIZEFLAG= "3" width="32" height="32"><a HREF="leo_TOC.html"><IMG SRC="arrow_rt.gif" WIDTH="32" HEIGHT="32" ALIGN="left" NATURALSIZEFLAG="3">

</BODY> </HTML>
- Added "Replace tabs with spaces" checkbox.

- The close button is equivalent to the "Cancel" button rather than the "Ok" button.
This was causing great confusion.
- There was a bug that prevented the command from working properly if the line number preceeded the present line.

- The goto line number option only works on full path names in @file nodes. It does not work for relative paths or in combination with an @path directive.
This problem only arose with languages such as html that have no single-line comment delimiter.
Leo did not properly handle configuration settings containing the % character.
Double-clicking the icon box used to set the cursor to the hand.
Every call to v.unjoinNode was calling c.clearAllVistied.

Fixing this bug will allow me not to use inodes & locations for the foreseeable future.
In the unusual case that a language has no single-line comment delimiter, Leo will remove the block delimiters from the headline text in node sentinels. createNthChild must take this into account by doing a second comparison of headlines if the normal comparison fails. If the second comparison of headlines succeeds we assume that we should use the headline from the outline instead of the derived file. I believe some kind of kludge like this will also be required for 4.0.
- Translating the "Tangle..." label differently from the "Tangle" one was impossible without modifying leo's source code (same thing for "Untangle..." and "Untangle").

- Couldn't translate either "Show Invisibles" and "Hide Invisibles". First toggle is OK then an error occurs after the second...
- changed copyPart so it returns on @code as well as @c.

- changed convertVnodeToWeb so that it takes acount of the "at_root_bodies_start_in_doc_mode" setting.

- For the most part, directives other than @c, @code, @space, @doc, @others get written to the file as @@directive. There are minor differences between how this is handled in @root and @file trees. This might be considered a bug, and I am not going to do anything unless someone complains loudly.
Enabling or disabling @language or @comment now recolors entire text.
I had forgotten to compile leo.nsi.
Python 2.3a1 contains a bug in the tkinter code. This bug has been fixed on Python's cvs site. Until this bug is fixed in an official Python release Leo must use:

true,false = 1,0

rather than:

true,false = True,False
leo.py 3.10 December 14, 2002

This version of Leo corrects bugs, improves Leo's documentation and adds many new features:

- Rewrote the Scripting Chapter in Leo's Users Guide. - Improved the Customization Chapter of the Users Guide. - Adds several customizations contributed by Leo's users. Among these: @read-only nodes. Reading empty .leo files. Redirecting stderr and stdout to Leo's log pane. - New Mark Clones and Go To Next Clone commands. - New Export Headlines command. - Control-dragging a node now clones it. - New "Node Only checkbox in the Find/Change panel. - @root-code trees start in code mode by default. This allows @root trees to work like @file trees. - @root-doc trees start in doc mode by default. - Converted leoConfig.py to use @root-code - New at_root_bodies_start_in_doc_mode option in leoConfig.py. Specifies whether @root trees work like @root-code or @root-doc. - New synonyms: @file-asis = @silentfile @file-nosent = @nosentinelsfile ; @file-noref = @rawfile. - Leo now supports navigation of menus using underlined menus. - Added an easy way to replace any Leo method by a function. The code in customizeLeo.py can now override any part of Leo's code! - Added routines to redirect stderr and stdout to Leo's log pane. These routines can be called from customizeLeo.py. - Added "event1" and "event2" hooks for customize event handling. - Improved Leo's window icon, and improved how that icon is loaded. - The usual assortment of bug fixes and minor improvements. Actually, this release fixes more than the usual number of bugs.

See the children of this node for full details.

The quote of the month:

I must say I am very impressed. I have been absolutely seduced by Leo over the past few days. I tell you, I can not put it down. I feel like a kid with a shiny new bike...I'm already bursting with new ways I'd like to use the tool in the future -- Lyn Adams Headley
The old code did not allow <tag /> in many cases. The new code reads minimal.leo without complaint. It is:

<?xml version="1.0" encoding="UTF-8"?> <leo_file> <leo_header file_format="2" /> <globals /> <preferences /> <find_panel_settings /> <vnodes /> <tnodes /> </leo_file>
Apparently the F4 shortcuts interferes with the Alt-F4 shortcut.
The content formerly on evisa.com, including the Leo tutorials is now on 3dtree.com.
Leo improperly added extra blank lines and tabs in @file-noset trees.
Untangle was warning about a missing section if the section had only an @doc part.
Leo was not handling the class keyword properly.
When leo.py was launched from the command line specifying a non-existent file the message "file not found" was displayed but the outline pane was not drawn properly.
@nosentinelsfile nodes were not being marked dirty properly.

Also, the message in atFile.writeAll about "no @file node found" did not include these nodes. The message was generalized.
While scanning for the initial state we must make sure that i < old_len and i < new_len.

Leo was not handling Python ''' strings properly.
It is not correct to do:

exec s in __builtins__

because that pollutes the namespaces of the __builtin__ module. Correct is simply:

exec s in {}
Previous versions of Leo did not properly recycle bindings in the outline pane.

This was reported by David LeBlanc and fixed with major hints from Chad Netzer.
The lambda functions for callbacks were not working properly.
The fix was made to atFile.scanAllDirectives() and leoGlobals.scanDirectives() tangle.scanAllDirectives() was not affected.

# Reversion fix: 12/06/02: We must use elif here, not if. elif not old.has_key("language") and dict.has_key("language"):
This bug was created recently by the new underline/realMenuName logic. We must not set the undo/redoRealMenuName ivars in clearUndoState: that should only be done when actually setting the menu names.
Both command are enabled only if the presently selected node is a clone. Mark Clones marks all nodes that are clones of the present node. Go To Next Clone goes to the next node that is a clone of the present node. A little experimentation convinced me that this command should wrap. So this command endlessly cycles among the nodes. Trying to end the this endless cycling would not be easy, as my experience with the Find command shows.

These are straightforward (and surprisingly useful) commands. Please report any problems immediately.
This works just like the Flatten Outline command except it writes only headlines.
How did I ever live without this.

Also improved messages and fixed a major performance bug: tree.numberOfVisibleNodes was being called repeatedly!
The Node Only checkbox takes precedence over Suboutline Only if they are both checked.
For the first time Leo allows you to choose whether body text in @root trees will start in code mode or doc mode by default. Ever since I created @file trees I've regretted the choice of making doc mode the default. Just last week I realized I'm not stuck with that old choice.

1. @root-doc filename and @root-code filename specify that body text is assumed to start in doc mode or code mode respectively. The options (-doc and -code) must follow @root immediately with no intervening whitespace. In effect, @root-code and @root-doc are two new directives. (They aren't syntax colored yet: they will be today or tomorrow.)

2. These @root options override the new at_root_bodies_start_in_doc_mode option in leoConfig.py. This option affects only @root trees without options. Such "plain" @root trees are now deprecated, which only means that it is now better style to use either @root-code or @root-doc. The reason is simple: the meaning of plain @root trees will depend on the at_root_bodies_start_in_doc_mode option, and that isn't good.

3. By default, at_root_bodies_start_in_doc_mode = 1 for compatibility for old @root trees. I actually don't think this option is good for much; I created it before I created @root-doc and @root-code, and I decided it wouldn't hurt to leave it in. Anyway, you now have complete flexibility about how @root works, and in particular you can make @root work just like @file.
@file-asis is a synonym for @silentfile. @file-nosent is a synonym for @nosentinelsfile. @file-noref is a synonym ffor @norefsfile.

Completed a major revision of the menu handling code to handle underlined menus. As is typical on Windows, you specify the underlined character by preceding it with an ampersand character.
Created a new icon and improved the code that loads it.
This is the funcToMethod function in customizeLeo.py.
See the relevant section of customizeLeo.py
"event1" and "event2" are generated for many Leo events. keywords.get("kind") returns one of the following for "event1" and "event2" hooks:

"activate", activate window event (not uniform across window managers). "deactivate" deactivate window event (not uniform across window managers). "boxClick" The +- box has been clicked. "drag" Generated repeated while dragging nodes. "endDrag" mouseUp that ends a drag. "headClick" Left-click in a headline. "headPopup" Right-click in a headline. "headKey" A keypress in a headline. "hyperLinkControlClick" Control-click on a hyperlink. "hyperLinkEnter" Mouse moves over a hyperlink. Disabled at present. "hyperLinkLeave" Mouse moves away from a hyperlink. Disabled at present. "iconDoubleClick" The icon box has been double-clicked.
The present code properly handles undo/redo for operations that copy trees, but copying trees itself causes problems for further undoes. I've disabled further undos, as was done previously, but this is a pity.

To fix this, we may have to hack on the beads to indicated that a copy has been made. No doubt about it: copying trees is tricky.
leo.py 3.9 Nov 23, 2002

This is one of the most interesting versions of Leo ever released:

* Major performance improvements: - Incremental syntax coloring speeds response when typing. - Incremental undo drastically reduces memory used.

* Major improvements in customizing Leo: - Leo looks in the file customizeLeo.py for user-specific customizations. - Leo calls "hook routines" in customizeLeo.py for all commands and events. These hook routines can modify or replace any command or event. - New convenience routines simplify creating and modifying menus. - Easy translation of menus to other languages using customizeLeo.py.

* Better integration with other tools: - The Open With command allows you to communicate back and forth to other editors. - The Go To Line Number command converts lines numbers in derived files to locations in the outline. - Double-clicking the icon of a @url node launches any url in your web browser. - The Execute Script command executes Python code in any body pane. This is much more convenient than opening Leo's Idle window. - Much better support for cweb. At last, Leo has a power user of cweb. By default, @space, @* and @** sections are colored as latex text. Leo properly generates @q..@> sentinels. - @nosentinelfile is like @file except Leo writes no sentinel lines.

* Other improvements: - Optional Emacs-style auto-indent. - Reformat Paragraph command. - Leo will optionally create nonexistent directories specified in @file, @root or @path, including all nonexistent parent directories. - Leo will place its own icon in Leo windows provided that you have installed Fredrik Lundh's PIL and tkIcon packages. - The usual assortment of bug fixes.

See the children of this node for full details.

P.S. Here is what Travers A. Hough recently said about Leo:

"When first I opened Leo, it was out of curiosity. But having used it...I'LL NEVER GO BACK. THEY'LL HAVE TO PRY LEO OUT OF MY COLD, DEAD FINGERS! Seriously, it should be renamed "Crack Cocaine" because it's that addictive. I'm ready to start a 12-Step group."
The cweb prefs panel setting was overriding @language python (!)
@language pascal @color

private uses
app().log is now set correctly when there are multiple windows. Previously, app().log depended on activate events, and was not reliable. The following routines now set app().log:

- frame.doCommand - frame.OnMenuClick

Thus, top() will be reliable after any command is executed. Creating a new window and opening a .leo file also set app().log correctly, so it appears that all holes have now been plugged.

Note: The value of top() may change during a new or open command, which may change the routine used to execute the "command1" and "command2" hooks. This is not a bug, and hook routines must be aware of this fact.
Leo no longer massages comments when importing Java files.

There was a bug in massageBody that prevented text from being undented properly. There was a bug in find_line_start (!!) that caused the start of the _previous_ line to be found if s[i] was a newline. It's amazing that these bugs were undiscovered for so long.

Treat interface like class.
Version 3.9 contains many new features and commands. The children of this node document them.
Undo now uses incremental syntax coloring. This speed up undo tremendously.
Previous versions of Leo stored the complete body text whenever any change was made to the body text. The present version of undo stores only enough information to undo or redo the typing: typically two lines of text. This results in about a 90% reduction in the memory used by undo.
These are discussed in the child of this node.
@nocolor

You may customize Leo in several ways:

I. Using leoConfig.txt

When Leo first starts, Leo looks for a file called leoConfig.txt, derived from leoConfig.leo, which contains extensive documentation for each setting. The settings in leoConfig.txt include:

- Fonts used in Leo's body, outline and log panes. - Colors used for syntax coloring. - Menu shortcuts for all menu items. - Sizes and splitter orientation for newly created windows. - Initial entries in the Recent Files menu, and the Find, Preferences and Compare panels. - The format of .leo files.

The section called "Using leoConfig.leo and leoConfig.txt" gives the rules for how Leo uses these two files.

II. Using plugins and hooks

New in 3.11: It is no longer necessary to modify customizeLeo.py. Instead of modifying customizeLeo.py you now should add plugins to the plugins directory. In fact, customizeLeo.py will soon disappear entirely.

During startup, Leo looks for plugins, i.e., files whose name matches mod_*.py in the plugins directory. These plugins register functions called "hooks" to be called a various times during execution. Full details are provided below.

Plugins provide the following advantages:

- Any time you see a nice hook handler that someone has written you can just copy it to a plugin file and it runs next time you start Leo. - You don't have to keep updating a large and growing customizeLeo.py file. - You can disable and enable hooks by renaming the relevant plugin file. (or by enabling or disabling the code in the plugin that registers hook functions). - You don't have to merge code from hooks pertaining to the same Leo event.

When executing any command or handling any event, Leo calls hook routines registered from the plugin files. The arguments to hooks are:

- "tag", a string telling the kind of command or event about to be executed, and - "keywords", a Python dictionary whose keys depend on the specific command or event.

This is a very simple, powerful and general mechanism for customizing Leo as you see fit. There are dozens of kinds of hooks, including the "command1" and "command2" hooks that are called before and after each of Leo's menu commands. Leo will allow you to override most commands and event handling. In many cases, if a hook returns any value except None Leo will assume that the hook has completely handled the command or event and will take no further action. The details will be explained in full below.

Leo catches all exceptions raised in hook code, so you can modify hooks without worry.

SECURITY WARNINGS: Using hooks naively can create the potential for virus-like behavior. Please read the section called SECURITY WARNINGS for full details.

III. Using convenience routines called from hooks.

Hook routines can import any file in Leo's source code and execute routines in that file. Leo's contains a number of convenience routines designed to make common customization tasks easier. Hooks can use these routines to create your own menus, to translate menus into other languages, and to create entries in the Open With menu. These convenience routines are discussed in detail below.

IV. Setting app().realMenuNameDict inside hooks to translate menus into other languages.

This is discussed below.
@nocolor

Leo will override settings in .leo files if it finds a file called leoConfig.txt. Leo works just as before if it does not find a leoConfig.txt file.

You should make changes to leoConfig.txt by changing leoConfig.leo and then executing the Tangle All command. There are two advantages to changing leoConfig.txt in this manner. First, it is just easier to change leoConfig.leo. Second, all comments are lost in leoConfig.txt when Leo updates it, so using leoConfig.leo as the primary data file means you will always have the comments available.

Leo looks for leoConfig.txt first in the directory specified by the Python variable sys.leo_config_directory. You would typically set this variable in Python's sitecustomize.py file. If this variable does not exist, Leo looks in the directory from which Leo was loaded.

The child of this node contains an example of leoConfig.txt that shows all the options that may be set.

Settings in leoConfig.txt overrides preferences in .leo files, but only for those items actually in leoConfig.txt, so you can choose which settings you want to override. Also, a Leo ignores any setting in leoConfig.txt whose value is "ignore" (without the quotes). For example:

[prefs panel options] tab_width = ignore

If a setting is overridden, it is _not_ written to the .leo file when the outline is saved. Note that this does not change the file format: all previous versions of Leo will be able to read such .leo files.

The preceding is probably all you need to know to use leoConfig.txt. The following discuss some minor details:

1. When reading a .leo file, if a setting is found neither in leoConfig.txt nor in the .leo file, Leo uses a default, hard-coded value. In leo.py 3.0 and later these default settings are found in tables that appear in the section called:

<< define default tables for settings >>

in the file leoConfig.py. So it is now convenient to change settings in leo.py itself as well as in leoConfig.txt.

2. Leo will update leoConfig.txt unless the read_only option is on in leoConfig.txt.

WARNING: there are problems when Leo does write leoConfig.txt: all comments are lost and options and sections are written in a random order. This is due to problems in Python's ConfigParser module and will not be changed any time soon.

3. Provided the read_only option is off, Leo updates leoConfig.txt whenever it saves a .leo file or whenever the Preferences panel is closed without being canceled. When updating leoConfig.txt, Leo will write only existing settings whose value is not "ignore".

4. When Leo saves a .leo file, Leo will write a Preferences setting to the .leo file only if the setting will not be written when updating leoConfig.txt. In particular, changes made in the Preferences Panel will become permanent immediately if Leo the read_only option is off. Otherwise the change will become permanent when any .leo file is saved.
Beginning with version 3.11, you may use plugins to customize how Leo works. You can

- override or modify any command - add or customize menus or - translate menus into any language.

Plugins have full access to all of Leo's source code. In particular, several convenience methods have been added to make customizing menus and commands easier.

Plugins are permanent: they will not go away when Leo is updated. You can take advantage of the latest CVS updates _without_ having to throw away your modifications.

Important warning: plugins must avoid blindly executing scripts in .leo files. Doing so could expose all parts of your computer to malicious Python code. The section called "Security warnings and precautions" discusses how to customize Leo safely and how to avoid several pitfalls.
@nocolor

New in 3.11. Leo now looks in the plugins folder for files whose name matches mod_*.py. Leo tries to import all such files at startup ("start2") time.

The child of this node gives an example of the code that creates the Open With menu. The following code, appearing at the end of mod_open_with.py, registers two different hook functions. The on_idle function is the "idle" hook. The create_open_with_menu is the "start2","open2" and "command2" hooks. @color

if 1: # Register the handlers... registerHandler("idle", on_idle) registerHandler(("start2","open2","command2"), create_open_with_menu) es("...open with")

@nocolor Plugins may call registerHandler with a hook name like "idle" or a list of hook names like ("start2","open2","command2"). Also, you can use the "all" hook name to indicate that code will be called for all hooks. This is useful for tracing hooks. Plugins may also call registerExclusiveHandler for hooks that should not be redefined in other plugins.

mod_open_with.py also contains the on_idle and create_open_with functions that actually handle the hook. Such functions have two arguments: tag and keywords. You will find many examples of this in LeoPy.leo.

About distribution:

1. LeoPy.leo now contains all the plugins in @ignore'd @file nodes. This way Leo won't complain when you open LeoPy.leo.

2. Most plugins actually are disabled: the code that registers the hooks is contained in #if 0: blocks. So to create a pluging you have to remove the @ignore and you have to change #if 0: to #if 1.

3. Most mod_*.py will never be part of Leo distributions, and the single-click installer will create an plugins folder containing just mod_open_with.py.

4. There is no longer a need for a customizeLeo.py file.

About error checking:

At present Leo will disable all hooks if any hook throws an exception. In practice this isn't a big deal, but obviously something better could be done. I won't do anything until after 3.11b1 goes out.

How to send me your plugins:

It should now be _much_ easier to submit a plugin for inclusion in LeoPy.leo. Just send me a .leo file containing @file mod_your_plugin.py. Please have this @file node be @ignored: this is more convenient for me. Ideally, your .leo file should contain _only_ your plugin code.
@color @language python @ignore

"""Open With handler"""

from customizeLeo import * from leoGlobals import *

@others

if 1: # Register the handlers... registerHandler("idle", on_idle) registerHandler(("start2","open2","command2"), create_open_with_menu) es("...open with")
# frame.OnOpenWith creates the dict as follows: # dict = {"c":c, "v":v, "f":f, "path":path, "time":time}

def on_idle (tag,keywords):

import os a = app() for dict in a.openWithFiles: path = dict.get("path") c = dict.get("c") v = dict.get("v") if path and os.path.exists(path): try: time = os.path.getmtime(path) if time and time != dict.get("time"): dict["time"] = time << update v's body text >> except: pass
<< set s to the new text >> body = v.bodyString() << set conflict flag >>

if conflict: # Report the conflict & set update. import leoDialog d = leoDialog.leoDialog() message = "Conflict in %s.\n\n" % (v.headString()) message += "Replace outline with external changes?" update = d.askYesNo("Conflict!",message) == "yes" else: update = s != body

if update: h = v.headString() es("changed:" + h) v.setBodyStringOrPane(s) c.selectVnode(v) v.OpenWithOldBody = s
try: # Update v from the changed temp file. f=open(path) s=f.read() f.close() except: es("can not open " + shortFileName(path)) break
try: # The OpenWithOldBody attribute does not normally exist in vnodes. old_body = v.OpenWithOldBody conflict = body != old_body and body != s except: conflict = v.isDirty() and body != s
def create_open_with_menu (tag,keywords):

if (tag in ("start2","open2") or (tag=="command2" and keywords.get("label")=="new")):

<< create the Open With menu >>

# Enable the idle-time hook so we can check temp files created by Open With. from leoGlobals import enableIdleTimeHook enableIdleTimeHook(idleTimeDelay=500)
@ Entries in the following table are the tuple (commandName,shortcut,data). data is the tuple (command,arg,ext). command is one of "os.system", "os.startfile", "os.spawnl", "os.spawnv" or "exec". 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 extension ext, Otherwise, Leo computes an extension based on what @language directive is in effect. @c

idle_arg = "c:/python22/tools/idle/idle.py -e "

if 1: # Default table. table = ( ("&Idle", "Alt+Shift+I",("os.system",idle_arg,".py")), ("&Word", "Alt+Shift+W",("os.startfile",None,".doc")), ("Word&Pad","Alt+Shift+T",("os.startfile",None,".txt"))) else: # David McNab's table. table = ( ("X&Emacs", "Ctrl+E", ("os.spawnl","/usr/bin/gnuclient", None)))

top().frame.createOpenWithMenuFromTable(table)
At startup time Leo looks all files of the form mod_*.py in the plugins directory. Leo assumes that these files are "plugins" containing Python modules that customizes Leo's operation. Leo attempts to import each file.

Each module should register routines that are called at various times during execution. Such times are identified by strings known as "tags". The code in in one or more plugins corresponding to each tag are known as the "hook" routines for that tag. Leo catches exceptions (including syntax errors) in hook routines, so it is safe to hack away on this code.

Leo passes two argument to all hook routines: the tag and a keywords dictionary containing additional information. For example, keywords["label"] indicates the kind of command for "command1" and "command2" hooks.

For some hooks, returning anything other than None "overrides" Leo's default action. Hooks have full access to all of Leo's source code. Just import the relevant file. For example, top() returns the commander for the topmost Leo window.

The following table summarizes the arguments passed to hooks: ( Overrides is "yes" if returning anything other than None overrides Leo's normal command or event processing.)

tag argument keys in keywords (hook name) overrides when called dictionary argument --------- --------- ----------- ------------------- "bodyclick1" yes before normal click in body c,v,event (new) "bodyclick2" after normal click in body c,v,event (new) "bodydclick1" yes before double click in body c,v,event (new) "bodydclick2" after double click in body c,v,event (new) "bodykey1" yes before body keystrokes c,v,ch,oldSel,undoType "bodykey2" after body keystrokes c,v,ch,oldSel,undoType "bodyrclick1" yes before right click in body c,v,event (new) "bodyrclick2" after right click in body c,v,event (new) "boxclick1" yes before click in +- box c,v,event (new) "boxclick2" after click in +- box c,v,event (new) "command1" yes before each command c,v,label (note 2) "command2" after each command c,v,label (note 2) "drag1" yes before start of drag c,v,event (new) "drag2" after start of drag c,v,event (new) "dragging1" yes before continuing to drag c,v,event (new) "dragging2" after continuing to drag c,v,event (new) "end1" start of app.quit() "enddrag1" yes before end of drag c,v,event (new) "enddrag2" after end of drag c,v,event (new) "headclick1" yes before normal click in headline c,v,event (new) "headclick2" after normal click in headline c,v,event (new) "headrclick1" yes before right click in headline c,v,event (new) "headrclick2" after right click in headline c,v,event (new) "headkey1" yes before headline keystrokes c,v,ch "headkey2" after headline keystrokes c,v,ch "hypercclick1" yes before control click in hyperlink c,v,event (new) "hypercclick2" after control click in hyperlink c,v,event (new) "hyperenter1" yes before entering hyperlink c,v,event (new) "hyperenter2" after entering hyperlink c,v,event (new) "hyperleave1" yes before leaving hyperlink c,v,event (new) "hyperleave2" after leaving hyperlink c,v,event (new) "iconclick1" yes before single click in icon box c,v,event (new) "iconclick2" after single click in icon box c,v,event (new) "iconrclick1" yes before right click in icon box c,v,event (new) "iconrclick2" after right click in icon box c,v,event (new) "icondclick1" yes before double click in icon box c,v,event (new) "icondclick2" after double click in icon box c,v,event (new) "idle" periodically (at idle time) c,v "menu1" yes before creating menus c,v (note 3) "menu2" yes before updating menus c,v "open1" yes before opening any file old_c,new_c,fileName (note 4) "open2" after opening any file old_c,new_c,fileName (note 4) "openwith1" yes before Open With command c,v,openType,arg,ext "openwith2" after Open With command c,v,openType,arg,ext "recentfiles1" yes before Recent Files command c,v,fileName,closeFlag "recentfiles2" after Recent Files command c,v,fileName,closeFlag "save1" yes before any Save command c,v,fileName "save2" after any Save command c,v,fileName "select1" yes before selecting a vnode c,new_v,new_v "select2" after selecting a vnode c,new_v,old_v "start1" no after app.finishCreate() "start2" after opening first Leo window c,v,fileName "unselect1" yes before unselecting a vnode c,new_v,old_v (new) "unselect2" after unselecting a vnode c,old_v,old_v (new) "@url1" yes before double-click @url node c,v (note 5) "@url2" after double-click @url node c,v (note 5)

Notes:

(1) "activate" and "deactivate" hooks: These have been removed because they do not work as expected.

(2) "commands" hooks: The label entry in the keywords dict contains the "canonicalized" form of the command, that is, the lowercase name of the command with all non-alphabetic characters removed.

Commands hooks now set the label for undo and redo commands "undo" and "redo" rather than "cantundo" and "cantredo".

(3) "menu1" hook: Setting app().realMenuNameDict in this hook is an easy way of translating menu names to other languages. Note: the "new" names created this way affect only the actual spelling of the menu items, they do _not_ affect how you specify shortcuts in leoConfig.txt, nor do they affect the "official" command names passed in app().commandName. For example, suppose you set app().realMenuNameDict["Open..."] = "Ouvre".

(4) "open1" and "open2" hooks: These are called with a keywords dict containing the following entries:

old_c: The commander of the previously open window. new_c: The commander of the newly opened window. fileName: The name of the file being opened.

You can use old_c.currentVnode() and new_c.currentVnode() to get the current vnode in the old and new windows.

Leo calls the "open1" and "open2" hooks only if the file is already open. Leo will also call the "open1" and "open2" hooks if: a) a file is opened using the Recent Files menu and b) the file is not already open.

(5) "@url1" and "@url2" hooks are only executed if "icondclick1" hook returns None.
Naively using hooks can expose you and your .leo files to malicious attacks. You will be safe as long as you follow these basic principles:

1. Hooks should _never_ blindly execute Python scripts in .leo files. Blindly executing scripts in .leo files runs the extreme risk of executing malicious code if you ever share any .leo file with someone else. Moreover, using rexec instead of exec WILL NOT PROTECT YOU. See the children of this node for further discussion of this important topic.

2. Hooks can execute any code safely _provided_ that all code is contained entirely within the plugin file. If you have a nifty customization that you want to do on a particular .leo file, put that code into a plugin.

3. Use only authentic copies of Leo that come from Leo's SourceForge site.
I'd like to thank Stephen Schaefer for gently insisting that we must guard against malicious code in shared .leo files. Here is a little story Stephen told on Leo's SourceForge site that makes clear what the danger is:

"I foresee a future in which the majority of leo projects come from marginally trusted sources...I see a world of leo documents sent hither and yon - resumes, project proposals, textbooks, magazines, contracts - and as a race of Pandora's, we cannot resist wanting to see "What's in the box?" And are we going to fire up a text editor to make a detailed examination of the ASCII XML? Never! We're going to double click on the cute leo file icon, and leo will fire up in all its raging glory. Just like Word (and its macros) or Excel (and its macros)."

This succinctly illustrates the fundamental problem: when we share "our" .leo files we can _not_ assume that we know what is our "own" documents. So hooks that naively searches through .leo files looking for scripts to execute is looking for big trouble.

The following node contains an example of the kind of code that is extremely dangerous.
@color @ WARNING ***** Using the following routine exposes you to malicious code in .leo files! *****

Do not EVER use code that blindly executes code in .leo files! Someone could send you malicious code embedded in the .leo file.

WARNING 1: Changing "@onloadpythonscript" to something else will NOT protect you if you EVER share either your files with anyone else.

WARNING 2: Replacing exec by rexec below provides NO additional protection! A malicious rexec script could trash your .leo file in subtle ways. @c if 0: # WRONG: This blindly execute scripts found in an .leo file! NEVER DO THIS! def onLoadFile(): v = top().rootVnode() while v: h = v.headString().lower() if match_word(h,0,"@onloadpythonscript"): s = v.bodyString() if s and len(s) > 0: try: # SECURITY BREACH: s may be malicious! exec(s + '\n',__builtins__,__builtins__) except: es_exception() v = v.threadNext()
@color @ It would be most foolish to expect rexec to protect you against malicious code contained in .leo files. Remember that Leo is a repository of source code, so any text operation is potentially malicious.

For example, consider the following script. This script is valid in rexec mode: @c

c = top() thisNode = c.currentVnode() v = c.rootVnode() while v: << change all instances of rexec to exec in v's body >> v = v.threadNext() << delete thisNode >> << clear the undo stack >>

@ This script will introduce a security hole the .leo file without doing anything prohibited by rexec, and without leaving any traces of the perpetrating script behind. The damage will become permanent _outside_ this script when the user saves the .leo file.
pass
pass
pass
Hooks have full access to all of Leo's source code simply by importing it.

Moreover, several convenience methods have been added to make customizing menus and commands easier. The following paragraphs discuss these routines and how to use them.

The following routines enable and disable "idle" hooks. They are defined in leoGlobals.py.

enableIdleTimeHook(idleTimeDelay=100)

Enables the "idle" hook. After this routine is called Leo will call the "idle" hook approximately every idleTimeDelay milliseconds. Leo will continue to call the "idle" hook periodically until disableIdleTimeHook() is called.

disableIdleTimeHook()

Disables the "idle" hook.
The following convenience routines make creating menus easier.

These are methods of the leoFrame class. Use top().frame to get the frame object for the presently active Leo window.

The plugins node in LeoPy.leo gives examples of how to use these routines to create custom menus and to add items to the Open With menu.

These convenience methods all do complete error checking and write messages to the log pane and to the console if errors are encountered.
@nocolor createMenuItemsFromTable (self,menuName,table,openWith=0)

This method adds items to the menu whose name is menuName. The table argument describes the entries to be created. This table is a sequence of items of the form (name,shortcut,command).

- name is the command name - shortcut is the shortcut, or None to indicate no shortcut. - command is the routine to execute when the menu item is selected.

An entry of the form ("-",None,None) indicates a separator line between menu items.

For example: @color table = ("Toggle Active Pane","Ctrl-T",self.OnToggleActivePane), ("-",None,None), ("Toggle Split Direction",None,self.OnToggleSplitDirection))

top().frame.createMenuItemsFromTable("Window",table) @nocolor

If the openWith keyword argument is 1 the items are added to a submenu of the Open With menu. However, it will be more convenient to use the createOpenWithMenuFromTable method to create the Open With menu.
createNewMenu (self,menuName,parentName="top")

This method creates a new menu:

- menuName is the name of the menu to be created. - parentName is the name of the parent menu, or "top" if the menu is to created in the menu bar.

This method returns the menu object that was created, or None if there was a problem. Your code need not remember the value returned by this method. Instead, your code will refer to menus by name.
createOpenWithMenuFromTable (self,table)

This method adds items to submenu of the Open With menu item in the File menu.

The table argument describes the entries to be created. This table is a sequence of items of the form (name,shortcut,data).

- name is the command name - shortcut is the shortcut, or None to indicate no shortcut. - data is a tuple of the form (command,arg,ext) - command is one of "os.system", "os.startfile", "os.spawnl", "os.spawnv" or "exec". - arg is an argument to be passed to the given command. - path is the full path name of the temporary file to be passed to the external editor. - ext is a file extension or None.

When the user selects the Open With item corresponding to the table item 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 what @language directive is in effect.

For example:

table = ( ("Idle", "Alt+Shift+I",("os.system",idle_arg,".py")), ("Word", "Alt+Shift+W",("os.startfile",None,".doc")), ("Wordpad","Alt+Shift+T",("os.startfile",None,".txt")))

top().frame.createOpenWithMenuFromTable(table)
deleteMenu (self,menuName)

Deletes the menu whose name is given, including all entries in the menu.
deleteMenuItem (self,itemName,menuName="top")

Deletes the item whose name is itemName from the menu whose name is menuName. To delete a menu in the menubar, specify menuName="top".
@nocolor The leoFrame class creates the Window menu as follows: @color

windowMenu = self.createNewMenu("&Window")

table = ( ("&Equal Sized Panes","Ctrl-E",self.OnEqualSizedPanes), ("Toggle &Active Pane","Ctrl-T",self.OnToggleActivePane), ("Toggle &Split Direction",None,self.OnToggleSplitDirection), ("-",None,None), ("Casca&de",None,self.OnCascade), ("&Minimize All",None,self.OnMinimizeAll), ("-",None,None), ("Open &Compare Window",None,self.OnOpenCompareWindow), ("Open &Python Window","Alt+P",self.OnOpenPythonWindow))

self.createMenuEntries(windowMenu,table)
@nocolor

It is easy for hooks to translate menus into another language. For example, code similar to the following code would typically be found in the "start2" hook:

@color

table = ( ("Open...","&Ouvre"), ("OpenWith","O&uvre Avec..."), ("close","&Ferme"), ("Undo Typing","French &Undo Typing"), # Shows you how much French I know ;-) ("Redo Typing","French &Redo Typing"), ("Can't Undo", "French Can't Undo"), ("Can't Redo", "French Can't Redo")) # Call the convenience routine to do the work. app().setRealMenuNamesFromTable(table)
Leo creates nonexistent directories only if create_nonexistent_directories = 1 in leoConfig.txt.

Previous versions of Leo created a new directory only if its parent directory already existed. Now, Leo will create all non-existing directories as required.

For example. Suppose neither dir1 nor dir2 exists. Leo will create both dir1 and dir1 when it writes the following @file \root\dir1\dir2\spam.py.
Double-clicking the icon box of a node whose headline has the form:

@url <any url>

executes the url in your default web browser.

Leo checks that the url is valid before doing so. A valid url is:

-- 3 or more lowercase alphas, -- followed by one ':', -- followed by one or more of: $%&'()*+,-./0-9:=?@A-Z_a-z{}~ -- followed by one of: $%&'()*+/0-9:=?@A-Z_a-z}~

Url's should contain no spaces: use %20 to indicate spaces.

You may use any type of url that your browser supports: http,mailto,ftp,file,etc.
The present version should be quite usable for its main purpose: tracking down lines corresponding to error messages. In particular, except for possible pathological cases, Leo should always put the cursor in the node containing the referenced line. Possible pathologies involve asking Leo to "go to" a sentinel line. It won't always be clear what node contains the sentinel.

I am well aware that Leo may not put the cursor on the proper line within the vnode, but human beings being the excellent pattern matchers that we are, we should be easily be able to find a line within a node.

Notes in LeoPy.leo, in the node called (Go to line number), discuss a variety of issues that may cause the cursor not to go to the "proper" line. It also discusses a better way to position the cursor.

Internally, there are two main versions of this command: one for @silentfile trees, and one for @rawfile and @file trees. In the first case, there are no sentinels in the derived file, so Leo just steps through the outline. This is the easy case, and should work well. In the second case (@rawfile and @file trees), the derived file contains sentinels, and Leo uses those sentinels to determine in what vnode the given line occurs.

In this second case, Leo can almost always position the cursor within the proper node, because Leo just scans back in the derived file looking for the proper @+node sentinel. However, in order to "count" lines in the derived file (to place the cursor properly _within_ the body text) Leo must recreate exactly the logic used by the atFile.read logic, and that isn't easy. Moreover, there is _no_ guarantee that the derived file is unchanged from when it was written; after all, if you get syntax errors you tend to want to fix them ;-)

Please do not report bugs in this command, unless the bug involves a crash, which would be reported both in Leo's log pane and in the console window. This command will probably be improved in 3.10.
At last Leo has a power user of cweb. Cweb mode is now fully tested and fully functional. The following improvements have been made to cweb mode:

1. Corrected sentinels used in cweb mode.

Leo must double _all_ @ signs following the @q@. For example, an @@color sentinel gets turned into @q@@@@@color@>.

2. Leo colors cweb doc parts and C comments in LaTex mode by default.

In cweb mode Leo colors @space, @* and @** sections using LaTeX coloring by default. The color_cweb_doc_parts_with_latex option in leoConfig.py/.txt affects this default.

Similarly, Leo colors C comments in cweb mode using LaTeX coloring by default. The color_cweb_comments_with_latex option in leoConfig.py/.txt affects this default.

3. Cweb mode works similarly in @file and @root trees.

See the child of this node for full details about cweb mode.
Cweb mode refers to how Leo tangles an outline when @language cweb is in effect or the cweb option in the Preferences Panel is in effect.

Leo treats _all_ cweb code in cweb mode as unevaluated text. That is, Leo treats cweb control codes, including @<...@>, @<...@>=, @c, @space, @* and @** as "raw" text within cweb mode. Leo does _not_ expand _cweb_ section references when writing derived files in cweb mode. However, Leo _does_ expand _noweb_ section references, so you may use noweb sections to organize cweb files! You can create _noweb_ code and doc sections using the @code and @doc directives in place of @c and @space directives.

By default, cweb colors @space, @* and @** sections using the same syntax coloring as for LaTeX. In addition, cweb colors C // and /*..*/ comments using LaTeX coloring by default. You may change these defaults using the color_cweb_doc_parts_with_latex and color_cweb_comments_with_latex options in leoConfig.py.
The Open With command allows you to communicate with external editor. When you select this command Leo creates a temporary file and invokes an external program. Leo periodically checks whether this temporary file has changed; Leo changes the corresponding node in the outline if so.

You must create the entries in the Open With submenu. This would typically be done in a hook routine, using the createOpenWithMenuFromTable routine described in the child of this node.

The @file mod_open_with.py node in LeoPy.leo gives a complete example of handling the Open With menu.
Leo now supports optional Emacs-style auto-indent. This style of auto-indent aligns newly created lines with unmatched ( [ or { brackets in the previous line. This feature is enabled only if smart_auto_indent = 1 in leoConfig.txt.

This code was contributed by David McNab, david@rebirthing.co.nz.
New in leo.py v3.9

Whether importing text from an outside source, typing in text, or modifying text, eventually you end up with a block of text in which some lines are too long and some are too short. The Reformat Paragraph command will rearrange the words in a text paragraph to fill each line as full as possible, up to the @pagewidth setting.

A paragraph is delimited by blank lines, Leo directives, and (of course) start and end of text in a node. The width of the line used by the reformatting operation is governed by @pagewidth and the indentation that would be applied to the node when tangled (as part of a @root) or written (as part of a @file).

The command operates on the paragraph containing the insert cursor. If the insert cursor is on a blank line or directive, nothing happens. If the cursor is on a line containing text, then the paragraph containing that text line is reformatted and the insert cursor is moved to the next paragraph.

Be careful that you don't do this on source code! Of course, if you do reformat something that should have been left alone, then just Undo.

NOTE: Lines with hanging indentation are handled so that the hanging indentation is preserved. This is most useful for bulleted or numbered lists, such as:

1. This is the first paragraph, and it has a hanging indentation.

2. This is the second paragraph, and it too has a hanging indentation.
An @rawfile tree is a tree whose root headline starts with @rawfile <filename>. Similarly, for @silentfile and @nosentinelsfile trees.

Leo creates files derived from @file and @nosentinelsfile trees in exactly the same way. The _only_ difference is that files derived from @file trees contain sentinels, while files derived from @nosentinelsfile trees do not. Therefore, Leo can not update outlines from changed made from files derived from @nosentinelfile trees.

Leo creates files derived from @rawfile and @silentfile trees by writing the body text of all nodes of the tree in outline order. Leo writes the body text _as is_, without recognizing section definitions, without expanding section references, and without treating directives specially in any way. In particular, Leo copies all directives, including @space or @c directives, to the derived file as text. However, Leo recognizes the @ignore directive in the _ancestors_ of @rawfile or @silentfile nodes, so you may use the @ignore directive as usual to prevent Leo from writing @rawfile and @silentfile trees.

There are several difference between @rawfile and @silentfile trees:

1. Leo writes no sentinel lines when writing @silentfile trees. When writing @rawfile trees, Leo writes only the @+leo, @-leo, @+node, @-node, @+body and @-body sentinels. Therefore, Leo can update @rawfile trees, but _not_ @silentfile trees from changes made in derived files. Unlike @file and @rawfile trees, the primary source of information for an @silentfile tree is the outline containing it.

2. Within @silentfile trees only, if a headline starts with @@, Leo writes everything in the headline following the @@ just before the corresponding body text.

3. Files derived from @silentfile trees contain _nothing_ not contained in body text (or @@ headlines). In particular, if body text does not end in a newline, the first line from the next node will concatenated to the last line of the preceding node.

In short, you can get any combination of sentinels/no sentinels and references/no references using @file, @nosentinelsfile, @rawfile and @silentfile trees. This is shown in the following table:

Derived files Sections and Type of tree contain sentinels? @others expanded? ------------ ------------------ ----------------- @file yes yes @nosentinelsfile no yes @rawfile yes no @silentfile no no
leo.py 3.8 October 29, 2002

Many major improvements have been made to Leo, all made as the result of user requests:

- New @rawfile and @silentfile trees complement @file trees. @rawfile and @silentfile trees create derived files without using sections. @rawfile files contain only those sentinels needed to automatically untangle them. @silentfile trees contain no sentinels at all. - New @raw and @end_raw directives allow unformatted code within @file trees. - Much better support for CWEB. You may organize .w files using noweb! - The @comment and @language directives may now apply simultaneously. - Leo can now read, remember and write <?xml-stylesheet...?> lines, under control of the new stylesheet option. - The new output_newline option specifies the line endings used when writing files. - The new output_initial_comment option secifies the text to appear in an @comment sentinel at the start of each derived file. - New commands in the help menu make it much easier to change configuration options. - Dozens of minor improvements and bug fixes.

This month Dave Hein and I discovered how to uniquely and permanently identify nodes, regardless of where those nodes are located and regardless of how many people are working simultaneously on them. This discovery will greatly improve Leo, and will simplify how people use Leo with CVS. Leo 4.0, due out in a month or two, will be the first version of Leo to use this breakthrough.

See the children of this node for full details.
The warning for "unknown language" had an unbound var.
The new code must check for @raw and @end_raw directives.
The << Set path from @file node >> code was assuming that v.isAtFileNode() was non-Null. This is no longer true with @rawfile. The traceback was:

File "c:\prog\leoCvs\leo\leoAtFile.py", line 424, in scanAllDirectives name = v.atFileNodeName() AttributeError: 'NoneType' object has no attribute 'atFileNodeName'

The correct code is:

if v.isAtFileNode(): name = v.atFileNodeName() else: name = v.atRawFileNodeName()

dir = choose(name,os.path.dirname(name),None)
Changes made:

1. Removed unhelpful warnings about non-existent paths. Only errors are given now. 2. Added logic to set self.root_name if it has not already been set. Without this, it is impossible to test for relative filenames in @root directives, and errors happen only after tangling is complete, which is quite confusing. 3. Added error messages when a "computed" file name does not exist. 4. Removed unused self.default_directory ivar from leoTangle.py. 5. Set self.tangle_directory ivar to base directory when @root node specifies a relative directory. This is required so the later code will try to create the proper file. 6. Renamed the path_directive_creates_directories option to create_nonexistent_directories. This makes clearer what is going on.
Leo now properly handles @delims directives in @file trees.
Leo now properly recognizes all sentinel lines when @language cweb is in effect.
Fixed a bug that caused Leo to say "methods" rather than "functions" when importing C code.

Leo now adds @root <filename> statement when importing to @root nodes.
Previously, Java only had block comments, and the ending delim wasn't being removed from doc parts.
The present code explicitly eliminates control-T processing in headlines, which means that control-T does not switch panes when editing headline text. Control-T _does_ switch panes in all other cases.

This appears to be another Tk bug.
Only shortcut pastes worked before.
Leo 3.7 significantly offers you several new ways to create derived files.
@rawfile and @silentfile trees are important new addtions to Leo. They work like @file trees, but are simpler to use. See the child of this node for full details.
The @raw and @end_raw directives delimit a region of "raw" text. No section references are recognized within "raw" text, and no additional leading whitespace is generated within "raw" text when writing the derived file.

The @quiet directive is valid only within @root trees. It suppresses all sentinels except the @+node sentinels used to mark headlines.

See the children of this node for full details.
The @raw and @end_raw directives are valid only within @file trees. The @raw directive starts a section of "raw" text. The @end_raw directive ends such a section, as does the end of body text. No section references are recognized within "raw" text, and no additional leading whitespace is generated within "raw" text when writing the derived file.
The @verbose, @terse, @quiet and @silent directives determine how the Tangle command outputs comments in @root trees. Comments written by the user in code sections are always output: these directives control only: a) the comments containing doc sections and b) sentinel comments that delimit the beginning and end of code sections.

When @verbose is in effect Tangle outputs all comments. When @terse is in effect, Tangle outputs only those comments necessary for Untangle to work. When @silent is in effect Tangle adds no additional comments. The @quiet directive is like @silent except that it does output leading sentinels as comments. Like @silent, @quiet inhibits untangling.

@verbose is the default. If more than one of these directives appear in the same body text the "most verbose" of these options will be in effect.
It is now possible to create CWEB (.w) files from @file trees. When @language cweb is in effect the following happen:

1. Leo does not recognize @space and @c directives.

2. The colorizer now recognizes both cweb and noweb section definitions and references.

3. Leo uses @q@ and @> for comment delimiters in files derived from @file or @rawfile trees, and Leo replaces @ by @@ within such comments.

4. Leo now properly warns about orphan nodes when @langauge cweb was in effect.

See the child of this node for the new documentation that applies to cweb mode.
It is now possible to have both the @comment and @language directives to be in effect at the same time. To do this, both directives must appear in the same node, and the @comment directive must follow the @language directive.
Leo preserves the <?xml-stylesheet...?> line if it is already present in the .leo file.

Otherwise, Leo writes a <?xml-stylesheet...?> line only if the stylesheet option is present in the configuration file. The stylesheet option specifies s in <?xml-stylesheet s?>
The Open LeoConfig.leo command opens the configuration file. The apply setting command applies any changes to settings made as the result of changing leoConfig.leo.
This option allows a comment to be iserted in derived files just after the initial @+leo line. The comment will appear in an @comment sentinel. See leoConfig.leo for full details.
This option allows you to specify the line endings to be used when writing derived files and when exporting files. See leoConfig.leo for full details.
Changed the path_directive_creates_directories option in leoConfig.leo to create_nonexistent_directories.

The old name was too misleading. Indeed, this option applies not just to @path directives, but to all situations in which the path name specified in @path directives or @root, @file, @rawfile or @silentfile nodes does not exist.

The new names are less cluttered than the old.
Leo now prints a summary of any exception taken to the log window, and a full traceback to the console window.
This is taken from Python Cookbook.

This may be of use to people developing Leo, though it's not clear how useful this is with Tk programs.
A small convenience that is actually quite nice to have.
Sentinel lines in Java files now start with //@.
It wasn't always completely clear when the command was complete.
Recently I have discovered a way of creating true, immutable global identifiers (called global tnode indices or gti's for short). Version 4.0, due late this year or early next, will be the first version of Leo to take full advantage of the opportunities created by gti's.

Gti's will be a way of _permanently_ and _uniquely_ identifying every node (headline and body text) ever created by Leo, even when many people are working on the same .leo and derived files. For the first time, Leo will be able _reliably_ to associate nodes in .leo files with nodes in derived files.

No previous version of Leo, including 3.7, has this ability, although that may not be so apparent to the casual user. As a work around in all previous versions of Leo, .leo files contain copies of all information in derived files. This "mirroring scheme" is needed so that clones in .leo files can be associated with their cloned counterparts in derived files.

While the mirroring scheme usually works well, it has some significant problems:

1. .leo files are much larger than they need to be.

2. Read errors occur if the derived files get out-of-synch with the information in the .leo file. All clone links get broken when read errors occur, which basically ruins the resulting .leo file. The only real way to recover is to use the Read Outline Only command.

3. Because out-of-synch conditions are so serious, it becomes a dicey proposition to save .leo files as backups; to create a reliable backup one must save the leo file along with all derived files.

Gti's solve all these problems:

1. .leo files no longer need to save a copy of all information in all derived files. Leo will write only the root node of @file trees to the outline, secure in the knowledge that it can reliably recreate the outline from the derived file.

2. The dreaded read errors will be impossible because there is no longer any data in the .leo file that can become out-of-synch with data in the derived files.

3. Because .leo files can never become out-of-synch with derived files, we can, for the first time, safely backup a project by creating a "fat" .leo file that contains all information in all derived files. Most .leo files will be "thin." Saving "fat" .leo files will only happen as the result of a new Save Backup command.

I am excited about Leo 4.0. It promises to revolutionize how people use Leo.
leo.py 3.7 October 9, 2002

Many improvements have been made to Leo. The highlights:

- Now works with Python 2.1. Previous versions required Python 2.2. - Many new user options, including user-definable shortcuts for all commands and colors for all parts of Leo's windows. As a result, it is now possible to use arrow keys as you desire. - Minimal support for Unicode Latin-1 (iso-8859-1) encoding. Better support is coming. - Improved handling of relative path names. - The @first directive allows lines to precede the opening @+leo sentinel line. - The @last directive allows lines to follow the closing @-leo sentinel line. - Better support for Perl+pod. - Leo now ends all lines with '\n' regardless of platform. - Leo is now compatible with Python 2.1. - Simplified sentinels reduce spurious CVS diffs. - Many other minor improvements and bug fixes.

Warning: Leo expects that all characters in derived files and .leo files are contained in the encoding specified by the xml_version_string option in leoConfig.txt. Characters will be garbled if this restriction is not followed. This restriction will be removed in the next release.

See the children of this node for full details.
The old code did not handle @comment /* */ in C mode because the */ given in the comment improperly terminated the scanning.

Changed find to rfind when scanning @@ sentinels.
Under certain circumstances # was not properly colored when coloring plain text.
single quote colorized text in plain text mode. This was due to missing parentheses.
The old code did not properly set the text selection in all cases.

This bug affected the Indent, Unindent, Extract and Extract Section.
Body text was being cleared in a node in an @file tree if the node contained a reference to a section defined in a grandchild. This was the first serious bug ever reported in the @file read logic.

The problem: Body text was being updated as the result of @+node sentinels rather than @+body sentinels. This was wrong, as extra @+node sentinels are often generated when no body text is generated. The result is that the body text was being cleared.

The fix: I simply moved the code that creates body text from the @+node logic to the @+body logic in scanText.
The new code resets the error count when reading each derived file, so errors in one file do not cascade to following derived files.
st_enter was not returning part numbers, which would have affected the Untangle command.
The old code undid the changes to only one headline of a group of cloned nodes. This was a serious bug because it would destroy the assumed eqquality between all cloned headlines.
The Find Previous command would get stuck once it found a match.
The original bug report:

"If you set the @tabwidth to something other than the Preference value, you'll see the body displayed appropriately. However, if you do a Ctrl-s to save the leo file, the body is redisplayed using the Preferences setting--of course, clicking on another node and then back to the original node displays with the correct tab setting again."
The old code remembered the insertion point only if it were set as the result of a keystroke.
This was too confusing to users. setup.py is intended only to create official distributions.
These improvements are listed in the approximate order of importance.
path_directive_creates_directories

Specifies whether to create a directory mentioned in an @file, @root node or @path directive if it does not exist.
relative_path_base_directory

The directory to be used as a prefix for <filename> in @path <filename> and @file <filename> and @root <filename> when <filename> is a relative path.

Valid values for this option:

"!" means relative to the location leo.py (the default). "." means relative to the location of the .leo file in the top window. An absolute path (in platform-dependent format).

Relative paths are not allowed: such paths would be dangerous because their meaning would depend on the changing value of the current working directory.
remove_sentinels_extension

Sets the string to be appended to file names resulting from the Remove Sentinels command. If the value starts with . the extension is appended to the original file name. Otherwise, the extension is appended before the file extension.

Example 1: File name x.y remove_sentinels_extension = _ns Result: x_ns.y

Example 2: File name x.y remove_sentinels_extension = .txt Result: x.y.txt
write_clone_indices

Indicates whether derived files will contain clone indices.

Please specify no clone indices if you are going to submit files to Leo's CVS site.
The following options now exist in the << Options for newly opened windows >> section of LeoConfig.leo.

initial_horizontal_secondary_ratio

Sets the ratio of outline pane size to log pane size when splitting the primary panes horizontally. The primary panes are the body pane and the secondary pane.

initial_vertical_secondary_ratio

Sets the ratio of tree pane size to body pane size when splitting the primary panes vertically. The primary panes are the body pane and the secondary pane.

The following options now exist in the << Window options >> section of LeoConfig.leo.

body_text_foreground_color body_text_background_color

The foreground and background colors of body text.

body_cursor_foreground_color body_cursor_background_color

The foreground and background colors for the cursor in body text.

headline_text_unselected_foreground_color headline_text_unselected_background_color

The foreground and background colors of unselected headline text. Both must be specified for either to take effect.

headline_text_selected_foreground_color headline_text_selected_background_color

The foreground and background colors of selected headline text that is not being edited. Both must be specified for either to take effect.

headline_text_editing_foreground_color headline_text_editing_background_color

The foreground and background colors of unselected headline text in a headline that is being edited. Both must be specified for either to take effect.

headline_text_editing_selection_foreground_color headline_text_editing_selection_background_color

The foreground and background colors of selected text headline text in a headline that is being edited. Both must be specified for either to take effect.

outline_pane_background_color

The background color of outline pane itself.

log_text_foreground_color log_text_background_color

The foreground and background colors of log text.
The << Keyboard shortcuts settings >> section of leoConfig.leo allows the user to specify keyboard shortcuts for all commands.

Notes:

1. There is one option for every command in Leo's menus; they won't be listed separately here.

2. For full documentation, see the

<< Keyboard shortcuts settings >> and << About keyboard shortcuts >>

sections of leoConfig.leo. The gist of this documentation is that you must use names known to Tk, and Leo also provides a few other names.

3. At most one shortcut may be associated with a command. If one really wanted to have two shortcuts, one would have to use two different command names. In particular, there is now only one shortcut for the Find Next command.
Leo checks all characters that are entered (via typing or via paste) into headline and body text to see whether they can be represented in the encoding specified by the xml_version_string setting in leoConfig.txt. Leo issues a message and _disallows_ the entry of the character if it can't be represented in that encoding.

This draconian behavior shouldn't be too much of a nuisance, at least temporarily. Note that _all_ characters are valid in the UTF-8 encoding, so the present version of Leo will allow you to specify all characters if you must. But the Latin-1 (iso-8859-1) encoding, being a one-byte encoding, limits you to at most 256 separate characters.

In 3.8 I plan to allow you to use all Unicode characters, regardless of encoding, by using character references. This will require some reorganization of the present code, and I don't want to delay 3.7 to handle them.

Warning: The present code does not handle well files (.leo or derived files) that contain characters outside the specified encoding. At present, your best bet is to remove such characters for now. Hopefully 3.8 will do a much better job handling characters "foreign" to the specified encoding.
Leo now writes only \n (Linux newlines) in derived files and .leo files. This was done simply by opening derived files and .leo files in "wb" mode.
1. Leo now supports relative_path_base_directory and path_directive_creates_directories user options. See the discussion of those options in this document.

2. Leo now retains file modes when tangling and writing derived files.
Blank lines now preceed and follow =pod and =cut.

Improvements made to @first and @last directives.
These were added so to resolve the debate about what the Esc key should do. The user can now bind Esc to either of these commands.
Leo no longer gives an error message when reading or writing cloned @file nodes.
Leo writes derived files a bit differently than before. These changes are compatible with the Borland version of Leo, provided that xml_version_string = UTF-8 in leoConfig.txt.

The changes:

1. Eliminated writing clone indices in derived files. This reduces the number of spurious changes reported by CVS. Leo now relies solely on information in .leo files to recreate clone indices. This is safe because read errors clear clone indices anyway. You can force clone indices to be written using the write_clone_indices user option in leoConfig.txt.

2. Removed extra newline following @+leo directive. This is part of an unfinished plan to eliminate all extra blank lines in derived files. It's not clear whether this plan will ever succeed.
Leo enables the Tangle and Untangle items in popup menus if any descendent of the selected node contains an @root node. Similarly, Leo enables the Read @file nodes and Write @file nodes items in popup menus if any descendent of the selected node contains an @file node.
leo.py 3.6 September 11, 2002

Highlights:

- Fixed a large number of bugs, especially bugs that appear only on Linux. - Leo now remembers the previous insertion point and location of the scrollbar when switching between nodes. - Added Match Brackets command. - Added support for MouseWheel (Linux only). - Added support for LaTex and PHP. - Improved all aspects of the Help menu. - Discovered a minor bug involving the Python (Idle) window and documented a workaround.

See the children of this node for full details.
The following minor bug can not easily be fixed because it involves an interaction between Leo and Idle.

After opening Idle with the Open Python Window command, opening another Leo window shows both the Leo window and a blank, square window. Closing either of the new windows may cause exceptions, either immediately or later.

The best workaround is simply not to open any new Leo windows after using the Open Python Window command.
os.rename can fail on some Linux systems.
When starting Leo with no arguments the 'NewHeadline' node was not displayed.
Changed:

bitmap_name = os.path.join(self.loadDir,"Icons\LeoApp.ico")

to:

bitmap_name = os.path.join(self.loadDir,"Icons","LeoApp.ico")
There were problems handling relative path names in @root and @path directives.
With Tk/Tcl 8.4, the Preferences window is empty (no buttons, fields, labels).

Tcl 8.4 complains about using "col" in the grid() calls.
This routine does a better job of checking the Tk version than the old code in leoApp.py
Only the last directive was in effect in @root trees. The new code allows node-by-node control.
This involved calling readlineForceUnixNewline instead of readline in the atFile:read logic.
How did I ever live without this?
The cursor now changes to an arrow when it moves over a link in the About Leo dialog.

The About Leo dialog now includes a build revision number.

Fixed problems with the Leo icon in the About Leo dialog.

Improved download dialog in Tutorial command.
CVS conflict files produce so many error messages that these errors are useless.
The Match Brackets command is enabled if the cursor is next to one of the following characters in the body pane:

( ) [ ] { } < >

This command looks for the matching character, searching backwards through the body text if the cursor is next to ) ] } or > and searching forward through the text otherwise. If the cursor is between two brackets the search is made for the bracket matching the leftmost bracket.

If a match is found, the entire range of characters delimited by the brackets is highlighted and the cursor is placed just to the left of the matching characters. Thus, executing this command twice highlights the range of matched characters without changing the cursor.
This code is due to Dave Hein.

Leo understands the following PHP keywords:

<?php, ?>, __CLASS__, __FILE__, __FUNCTION__, __LINE__, and, as, break, case, cfunction, class, const, continue, declare, default, do, else, elseif, enddeclare, endfor, endforeach, endif, endswitch, endwhile, eval, extends, for, foreach, function, global, if, new, old_function, or, static, switch, unset(), use, var, while, xor,

Leo does not yet color the following keywords containing():

array(), die(), echo(), empty(), exit(), include(), include_once(), isset(), list(), print(), require(), require_once(), return()
This code is due to Marcus A. Martin.

Leo understands the following LaTex keywords: capitalization is significant. For example, \vert produces a single vertical line and \Vert produces a double vertical line.

\@, \(, \), \{, \},

\acute, \addcontentsline, \addtocontents, \addtocounter, \address, \addtolength, \addvspace, \AE, \ae, \aleph, \alph, \angle, \appendix, \approx,\arabic, \arccos, \arcsin, \arctan, \ast, \author,

\b, \backmatter, \backslash, \bar, \baselineskip, \baselinestretch, \begin, \beta, \bezier, \bf, \bfseries, \bibitem, \bigcap, \bigcup, \bigodot, \bigoplus, \bigotimes, \bigskip, \biguplus, \bigvee, \bigwedge,\bmod, \boldmath, \Box, \breve, \bullet,

\c, \cal, \caption, \cdot, \cdots, \centering, \chapter, \check, \chi, \circ, \circle, \cite, \cleardoublepage, \clearpage, \cline,\closing, \clubsuit, \coprod, \copywright, \cos, \cosh, \cot, \coth,csc,

\d, \dag, \dashbox, \date, \ddag, \ddot, \ddots, \decl, \deg, \Delta, \delta, \depthits, \det, \DH, \dh, \Diamond, \diamondsuit, \dim, \div, \DJ, \dj, \documentclass, \documentstyle, \dot, \dotfil, \downarrow,

\ell, \em, \emph, \end, \enlargethispage, \ensuremath, \enumi, \enuii, \enumiii, \enuiv, \epsilon, \equation, \equiv, \eta, \example, \exists, \exp,

\fbox, \figure, \flat, \flushbottom, \fnsymbol, \footnote, \footnotemark, \fotenotesize, \footnotetext, \forall, \frac, \frame, \framebox, \frenchspacing, \frontmatter,

\Gamma, \gamma, \gcd, \geq, \gg, \grave, \guillemotleft, \guillemotright,\guilsinglleft, \guilsinglright,

\H, \hat, \hbar, \heartsuit, \heightits, \hfill, \hline, \hom, \hrulefill,\hspace, \huge,\Huge,\hyphenation

\Im, \imath, \include, includeonly, indent, \index, \inf, \infty, \input, \int, \iota,\it, \item, \itshape,

\jmath, \Join,

\k, \kappa, \ker, \kill,

\label, \Lambda, \lambda, \langle, \large, \Large, \LARGE, \LaTeX, \LaTeXe, \ldots, \leadsto, \left, \Leftarrow, \leftarrow, \lefteqn, \leq, \lg, \lhd, \lim, \liminf, \limsup, \line, \linebreak, \linethickness, \linewidth,\listfiles, \ll, \ln, \location, \log, \Longleftarrow, \longleftarrow, \Longrightarrow,longrightarrow,

\mainmatter, \makebox, \makeglossary, \makeindex,\maketitle, \markboth, \markright, \mathbf, \mathcal, \mathit, \mathnormal, \mathop, \mathrm, \mathsf, \mathtt, \max, \mbox, \mdseries, \medskip, \mho, \min, \mp, \mpfootnote, \mu, \multicolumn, \multiput,

\nabla, \natural, \nearrow, \neq, \newcommand, \newcounter, \newenvironment, \newfont, \newlength,\newline, \newpage, \newsavebox, \newtheorem, \NG, \ng, \nocite, \noindent, \nolinbreak, \nopagebreak, \normalsize, \not, \nu, nwarrow,

\Omega, \omega, \onecolumn, \oint, \opening, \oval, \overbrace, \overline,

\P, \page, \pagebreak, \pagenumbering, \pageref, \pagestyle, \par, \parbox,\paragraph, \parindent, \parskip, \part, \partial, \per, \Phi, \phi,\Pi, \pi, \pm, \pmod, \pounds, \prime, \printindex, \prod, \propto, \protext, \providecomamnd, \Psi,\psi, \put,

\qbezier, \quoteblbase, \quotesinglbase,

\r, \raggedbottom, \raggedleft, \raggedright, \raisebox, \rangle, \Re, \ref, \renewcommand, \renewenvironment, \rhd, \rho, \right, \Rightarrow,\rightarrow, \rm, \rmfamily, \Roman, \roman, \rule,

\s, \samepage, \savebox, \sbox, \sc, \scriptsize, \scshape, \searrow, \sec, \section, \setcounter, \setlength, \settowidth, \settodepth, \settoheight, \settowidth, \sf, \sffamily, \sharp, \shortstack, \Sigma, \sigma, \signature, \sim, \simeq, \sin, \sinh, \sl, \SLiTeX, \slshape, \small, \smallskip, \spadesuit, \sqrt, \sqsubset, \sqsupset, \SS, \stackrel, \star, \subsection, \subset, \subsubsection, \sum, \sup, \supressfloats, \surd, \swarrow,

\t, \table, \tableofcontents, \tabularnewline, \tan, \tanh, \tau, \telephone,\TeX, \textbf, \textbullet, \textcircled, \textcompworkmark,\textemdash, \textendash, \textexclamdown, \textheight, \textquestiondown, \textquoteblleft, \textquoteblright, \textquoteleft, \textperiod, \textquotebl, \textquoteright, \textmd, \textit, \textrm, \textsc, \textsl, \textsf, \textsuperscript, \texttt, \textup, \textvisiblespace, \textwidth, \TH, \th, \thanks, \thebibligraphy, \Theta, theta, \tilde, \thinlines, \thispagestyle, \times, \tiny, \title,\today, \totalheightits, \triangle, \tt, \ttfamily, \twocoloumn, \typeout, \typein,

\u, \underbrace, \underline, \unitlength, \unlhd, \unrhd, \Uparrow, \uparrow,\updownarrow, \upshape, \Upsilon, \upsilon, \usebox, \usecounter, \usepackage,

\v, \value, \varepsilon, \varphi, \varpi, \varrho, \varsigma, \vartheta, \vdots, \vec, \vector, \verb, \Vert, \vert, \vfill, \vline, \vphantom, \vspace,

\widehat, \widetilde, \widthits, \wp,

\Xi, \xi,

\zeta
leo.py 3.5 August 14, 2002

Highlights:

- Added new entries in the help menu. (Including a link to Joe Orr's excellent ScreenBook tutorial). - Added the importFiles script in leoImport.py. - Added configuration options for horizontal scrollbars. - Added better warnings for read-only files. - Removed all '\r' when reading derived files. - Fixed crasher in Read Outline Only command. - Fixed crasher when leoConfig.txt did not exist. - Many small improvements and bug fixes.
This may be the cause of truncated leoConfig.txt files on Linux.
Removed '\r' characters when reading. The write logic is unchanged.
Relative paths should not set the global default paths. Instead, the relative path is concatenated to the global default path when the file is opened.
Added log_pane_wraps and outline_pane_scrolls_horizontally configuration settings.
Note: b4 contains a fix to b3 in this area: for writing, we must first test whether the file exists. Otherwise we may get a false indication that the (non-existent) file is read-only.
Added the importFiles script in leoImport.py. Use this script as follows:

- Select the node under which you want the imported files to appear. - Open the Python window within Leo. - Type the following in the Python window:

import leoImport dir = "c:/prog/test" # or some other directory type = ".py" # Type of files you want to import. leoImport.importFiles(dir,type)

If you want to import all files in a directory, replace the last line with:

leoImport.importFiles(dir)
leo.py 3.4 August 11, 2002

This release contains many small improvements and bug fixes. The highlights:

- Fixed several crashers involving Unicode. - The Show Invisibles command now makes blanks and tabs look much better. - Created leoConfig.leo, from which leoConfig.txt is now derived. - Added save_clears_undo_buffer configuration option. - Fixed a large number of problems with the configuration code. - The usual minor improvements and bug fixes.

See the children of this node for full details.
The problem is that the ratio is saved in the .leo file and the split direction is not. So if leoConfig.txt is read-only you the Toggle Split Direction command will permanently change the body-outline ratio _without_ permanently changing the split direction itself.

This basically sucks, and here are my reasons:

1. I do not want to be adding more options to .leo files; the whole idea of leoConfig.txt is to move in the opposite direction.

2. You can make everything work more like you would expect by making leoConfig.txt read-write.

3. Especially in the case of the split direction, I expect people to have strong preferences one way or the other, so even if leoConfig.txt is read-only it shouldn't be too odious to pick a split direction and live with it.
The undo buffer creates a lot of strings. On some system this may be a problem. If save_clears_undo_buffer = 1 the Save command (and only the Save command) will clear the undo buffer.
The new spelling is to limit_directory_search_extension. Note that this affects the valid entries in leoConfig.txt.
This allows us to create leoConfig.txt more easily, and provides a safe place to permanently store comments when read-write mode is on.
If a setting was missing or "ignore" it could never be set! This no doubt could have caused a wide variety of symptoms. In particular, in the distributed leoConfig.txt file it meant that settings for "default" fonts and sizes were never updated!
The new code properly deletes previous entries regardless of case or path separators.
There were a couple of bugs here.

Note that the ratio (whichever the direction) is stored in the .leo file. The initial_splitter_orientation setting in leoConfig.txt applies to _all_ files. The initial_vertical_ratio and the corresponding initial_horizontal_ratio apply only to new files, so are essentially useless.

Yes, this is pretty stupid; it's the result of not wanting to add more stuff to Leo's file format.
leo.py 3.1 contains Python code that may be of interest to some Python programmers. All Python files are derived from LeoPy.leo.

See the children of this node for details.
This script does much of the grunt work of converting from C/C++ syntax to Python syntax. It was surprisingly effective in converting Leo from C++ to Python. These scripts don't need to be perfect to save a _lot_ of work. And of course you can always add your own improvements...

As written, the script converts an entire Leo tree. It would be easy to write an entry that would convert plain text files, but it's probably best to import the files to be converted into Leo first. That way the slow algorithms in c2py.py will work on smaller pieces of text.
This code gathers and prints static statistics about Python bytecode.
The file leoUtils.py contains some small Tkinter utilities:

w,h,x,y = get_window_info(top) returns the width, height, x and y positions of the Tkinter window top.

center_dialog(top) centers the Tkinter window top on the screen.

w,f = create_labeled_frame(parent...) creates a labeled Tkinter window as a child of parent. The caller packs widgets into f to create the frame.

leoUtils.py contains many other routines including an Python version of the Sherlock tracing package.
leoFontPanel.py puts up a Font dialog. This dialog dispenses with the typical "sample" pane and instead changes text immediately directly on the screen. Comes with Ok, Cancel and Revert buttons. IMO, this dialog is much better than the sample Tk/Tkinter font dialogs available on the web.
leoColor.py contains code to syntax color Tk.Text widgets for a variety of languages. Supporting a new language here is mostly a matter of adding another table of keywords.
Leo supports powerful outlining features, including clones. Clones are challenging to implement, to say the least. The vnode and tnode classes, defined in leoNodes.py, represent the underlying data. vnodes represent nodes on the screen. vnodes may share text, the tnode class is the "unit of sharing" of such text. The leoTree class draws the tree and handles events, including dragging nodes around the screen. The Commands class calls the vnode, tnode and leoTree classes to handle menu commands. In Smalltalk's model/view/controller terminology, the vnode and tnode classes are model class, the leoTree class is a view class, and the Commands class is a controller class.
Now at last it is clear why Leo hasn't been writing derived files affected by clones. It's not enough to mark v dirty. We must mark all the cloned nodes of v's tree dirty as well.

However, we only need do this when a node is deleted, and we definitely do _not_ want to do this otherwise, for that would mark far too many nodes dirty. To get around this, we leave v.setDirty() as it was, and use the new code only in v.setDirtyDeleted.

Note that the undo and redo cases handle this automatically because they call c.doDelete.

So now we can state a rule that should not ever be broken: after doing a Save command, a Write @file Nodes command should always report "unchanged" for all files.
Added setRevertVars to update revert settings. This is called from __init__ and onOk.
Changed idle_body_key and idle_head_key, leoTangle::os and tnode::setTnodeText.

We must remove backticks from messages that might contain unicode characters so they will print properly. There were only a few instances, and they are all error messages that hardly ever get executed, so I hope I was careful putting them in. I should be able to find them all by looking for '"'.
Repeated double-clicking (without intervening single-click) caused the selection to move to the left.
This is consistent with what the Import commands do for the other file types. It's also much safer.
This makes selected text look much better and makes Show Invisibles look much better as well.

This was not easy to do. Apparently tag settings interfer with the Tk.Text settings. I have not found a way to specify the selectbackground color for tags. The only way I could get things to work is define whitespace tags only when in showInvisibles mode.
.tmp files disappear On Windows XP after Leo exits!
leo.py 3.3 August 3, 2002

This version of Leo:

- Added support for Unicode. - Improved the Import commands. - Fixed numerous bugs related to configuration settings. - Fixed the "Javadoc" bug and several latent bugs related to it.

See the children of this node for details.

Edward K. Ream
It is now possible to use non-Ascii characters in Leo!

Leo uses the xml_version_string setting to encode Unicode characters in .leo files. This will be UTF-8 by default. I've also very briefly tested ISO-8859-1.

I am a complete Unicode ignoramous. It seemed trivial to add Unicode support to Leo, and if there are any fine points that should have been considered I no doubt did not consider them...
The Import command now does a better job of removing excess leading whitespace.

When importing .java files, Leo now preserves newlines in leading block comments. This makes the typical weird Java comments look better. For example,

/** This is a typical, * brain-dead, * Java comment. */

Now gets redered as

@ * This is a typical * brain-dead, * Java comment. @c

The comment will be reconstituted properly when rewritten.
This never happened before because all languages had either 1 or 3 delims. But Java now has only 2 delims to force block comments.
Leo should now handle negative tab widths properly.
- Negative tab widths are now honored properly.

- Made 'additional_body_text_border' setting functional.

- Made 'body_pane_wraps' setting functional.

- There was a bug that made the following non-functional.

"read_only" "xml_version_string" "limit_count" "body_pane_wraps"

That is, only the default settings in leoConfig.py were used, not the settings in leoConfig.txt.

- The old code did not use the "page_width" and "tab_width" settings from leoConfig.txt.
leo.py 3.2 July 30, 2002

This version generalizes the @others directive, improves the Import command and fixes several bugs. See the children of this node for full details.

The highlights:

- Nested @others directives are now valid, an important improvement. This simplifies files that define more than one class. - Improved the Import command and squashed several bugs lurking there. - Made the various Go commands in the Outline-Move/Select menu functional by reassigning keyboard shorts - Fixed a crasher in the Prefs Panel. - Fixed numerous bugs in the Set Colors command. - Fixed syntax coloring of C strings that span multiple lines. - The usual minor improvements and bug fixes.

Edward K. Ream
Nested @others directives are now valid, even when no named node intervenes. This is a significant enhancement to Leo because it is now easier to define multiple classes in a single file. The leoNode.py file also uses nested @others directives.

The outline shown in the child of this node shows the general idea.

The Import command uses nested @others to great effect in Python files containing many classes. For example, try importing the file Python/Lib/Compiler/ast.py.
@color @ignore

# This is the "outer" @others @others
class a: # This is an "inner" @others. No named node intervenes. @others
def one(): pass
def two(): pass
class b: # This is an "inner" @others. No named node intervenes. @others
def one(): pass
def two(): pass
The Import command creates nested @others directives when parsing Python classes.

Fixed two bugs in the Import command:

1. The Import command now properly places the first @others directive of Python files. Previously the @others directive was placed before section references, so code could be rearranged.

2. The Import command now properly handles all C preprocessor directives. Previously there were problems handling the #ifndef directive which could ruin imports.

Two other minor improvements:

1. This is reasonable: we don't wan't people generating files from imported files unless they take some positive action.

2. The Import command now checks that different branches of #if, #else, #endif directives have the same net number of braces, and warns if they do not. This logic was inserted while trying to fix bug #2 above.
It's too confusing to have arrow keys mean different things in different panes.

For one thing, we want to leave the focus in the body pane after the first click in the outline pane, but that means that the arrow keys must still be functional in the _body_ pane!

Alas, all the various combinations of key bindings of arrow keys appear to do something; there are none left to use for moving around in the outline pane. So we are stuck with poor shortcuts.
Leo could sometimes crash when the user selected Cancel in the Preferences panel.
There were too many problems in the old color panel.

- Completely revised the Revert logic in the main Color panel. - The color name picker now reverts properly. - Color names and color swatches are now properly updated in all cases, so there is no need to write color values to the log window. - Eliminate quotation marks around color names and values. - The color picker now shows the value of the present color, not the revert color. - Increased the width of name buttons to handle long color names.
Thanks to Thomas Guettler for suggesting this.
This script uses the dis module to print static statistics about Python bytecode. Of no use to anyone but me.
leo.py 3.1 July 20, 2002

This version fixes a blunder that affects only leo.py 3.0. I recommend that all users of 3.0 upgrade to this version.

With this release Leo's to-do list is now empty! I shall fix bugs as they are reported. I'll add new features only if convinced that they contribute significantly to Leo.

The highlights:

- Fixed a blunder: Leo 3.0 did nothing when it was opened directly from leo.py. (Opening leo using openLeo.py did work.) - Created a compare panel to control scripts in leoCompare.py. - Added many new settings in leoConfig.txt to initialize the compare panel. - The FAQ tells how to add support for new languages. (These instructions also appear in the "Notes/How To" section in LeoPy.leo) - The usual minor improvements and bug fixes.

Edward K. Ream
The file comparison window helps people verify that imported files are, in fact, precisely equivalent to the original files.
Here are the new settings in leoCompare.txt:

# Initial settings of the Compare panel. # These correspond to the keyword parameters of the leoCompare constructor. # Exception: there are no keywords for compare file names in the constructor.

[compare options]

# ----- Options specifying files for the Compare panel.

compare_file_1 = c:/prog/test/compareTest1/file1.txt # Path to the first file or directory to be compared. # Directory compares ignore a filename part of the path, if present. # E.g., c:/directory1/spam.py is valid for directory compares.

compare_file_2 = c:/prog/test/compareTest2/file2.txt # Path to the second file or directory to be compared. # Directory compares ignore a filename part of the path, if present. # E.g., c:/directory2/spam.py is valid for directory compares

output_file = c:/prog/test/compareResults.txt # Path to the output file. # Leo will silently write to this file, regardless of whether it already exists. # Leo will write to the log pane if this path is empty or invalid.

append_output_to_output_file = 0 # 0: Replace output file with results of compare. # 1: Append output to output file.

# ----- Options related to directory compares. # These options has no effect when comparing files.

limit_directory_search_extenstion = .py # Limit directory searches to files with the given file extension. # Examples: # None Compare all files when comparing directories. # .py Compare .py files when comparing directories.

# ----- Options related to file compares. # These options have no effect when comparing directories.

ignore_blank_lines = 1 # 1: Ignore blanks lines when comparing files.

ignore_first_line_of_file_1 = 0 # 1: Ignore the first line of compare_file_1 when comparing files.

ignore_first_line_of_file_2 = 0 # 1: Ignore the first line of compare_file_2 when comparing files.

ignore_interior_whitespace = 0 # 1: Ignore whitespace after the leading whitespace of a line when comparing files.

ignore_leading_whitespace = 0 # 1: Ignore leading whitespace of each line when comparing files. # Not recommended when comparing .py files.

ignore_sentinel_lines = 0 # 1: Ignore sentinel lines when comparing files. # Leo sets sentinel comment delimiters from the first line of each file. # This option has no effect if the first line is not a @+leo line.

# ----- Options affecting how Leo shows the results of file compares. # (Leo gathers statistics regardless of these options.)

limit_count = 9 # 0: Show lines regardless of the number of mismatches. # n: Stop showing lines after n mismatches.

make_whitespace_visible = 0 # 1: Show blanks as [ ] and tabs as [t]

print_both_lines_for_matches = 0 # 0: Print only the line of compare_file_1 when showing matching lines. # 1: Print lines of both files when showing matching lines.

print_matching_lines = 0 # 1: Print lines that match using the print_both_lines_for_matches option.

print_mismatching_lines = 1 # 1: Print lines that do not compare equal to each other.

print_trailing_lines = 1 # 1: Print lines all lines in one file after an end-of-file is seen on the other file.
The new rules allow leo.py 3.1 to read and write .leo files more like leo.py 2.x when there is no leoConfig.txt file. The scheme used in leo.py 3.0 meant that preferences were _never_ written to .leo files. It's advantage was that all defaults were specified in:

leoConfig.<< define default tables for settings >>

I think the present scheme is more natural, but we shall see...

In effect, Leo sets and gets various options in the following order of precedence:

1. Use explicit settings in leoConfig.txt if they exist.

2. Use defaults for leoConfig.txt, but only if leoConfig.txt exists. These defaults are found in:

leoConfig.<< define default tables for settings >>

3. Use settings in .leo files if leoConfig.txt does not exist.

4. If leoConfig.txt does not exist, and the .leo file does not specify a setting, use hard-coded settings in the various classes, just as leo.py 2.x did.
The @pagewidth, @tabwidth and @language directives could not have worked while tangling. Apparently nobody has every used these directives in @root trees!
The Flatten Outline command must use an escape convention so that it can distinguish between the "+" and "-" characters that denote a headline and any "+" and "-" characters that start a line of body text.

The old version of Flatten Outline inserted a backspace before _all_ "+", "-" and backspace characters. The new version of Flatten Outline inserts a backspace before such characters only if they are the first non-whitespace character of a line.

This change affects the "file format" used by the Import Flattened Outline command. If you import a file flattened with the old version of Flatten Outline, the new Import Flattened Outline will remove the inserted backspace only if it is the first non-whitespace character of the line. In practice, this is unlikely to be a problem: you could just manually convert "\\", "\+" and "\-" to "\", "+" and "-" throughout the imported file.
The Open Python Window command now attempts to add IDLE's location to sys.path if import idle fails.

Bernhard Mulder contributed the code that made this improvement possible.

leo.py 3.0 July 16, 2002

This version is called 3.0 because it can optionally produce files that can NOT be read by the leo.py 2.x or the Borland version of Leo. However, by default, leo.py 3.0 DOES produce files that all previous versions of Leo can read.

See the children of this node for full details. The highlights:

- Support for many user options stored in leoConfig.txt, including, among others, fonts in all panes, colors for syntax coloring and default window size and position. - Support for .leo files with XML types like "ISO-8859-1", controlled by a user option. Note: by default, Leo writes files compatible with previous versions of Leo. - Powerful new Color and Font pickers, fully connected to user options. - Added Toggle Split Direction command, under control of user options. - Added autoscrolling in the outline pane. - Windows open at the position in which they were saved. - The size and position of new windows can be controlled with user options. - Eliminated drawing problems while opening files. - Improved syntax coloring for @comment plain. - The Convert All Blanks and Convert All Tabs commands are now undoable. - Leo warns and aborts if Python 2.2 or above is not running. - The usual bug fixes.

At least one more version of leo.py is planned.
You now have a choice: you can configure Leo to please previous versions of Leo or to please XML parsers:

Leo now uses the xml_version_string configuration setting, if present, to override the UTF-8 setting used in all previous versions of Leo. The default value of this setting is UTF-8, so by default leo.py will write files that may be read by any previous version of leo.py or the Borland version of Leo.

However, xml_version_string = ISO-8859-1 will produce .leo files that are more acceptable to XML parsers when those .leo files contain Unicode characters that are not found in the UTF-8 (Ascii) character set. In any event, the choice of whether to please previous versions of Leo or XML parser is completely up to you.

If the setting does not exist, whatever value is present in the .leo file will be written when the .leo file is written. For new files, a value of UTF-8 will be used when this setting is not present.

Notice that this also affects what is written to the clipboard.
This version supports many more options than before. See leoConfig.txt for the full list.

Leo looks for leoConfig.txt in the same places as in leo.py 2.5. If Leo does not find leoConfig.txt, This version will use defaults specified in tables defined in the section << define default tables for settings >> in leoConfig.py. So it is now convenient to change settings in leo.py itself as well as in leoConfig.txt.
Including the split direction and split ration.

Leo now uses all configuration settings from leoConfig.txt.
If you drag a headline above or below the outline pane the outline pane will scroll automatically. As before, Leo will cancel the drag if you release a headline anywhere but over another headline's icon.
You can specify colors for each syntax coloring construct using a color wheel or by choosing one of the standard Tk color names. When a color becomes active Leo writes its value or color name to the log pane for your reference. This is useful when setting coloring options in leoConfig.txt.
Using this panel should be self-explanatory (what you see is what you get) with the following exceptions:

1. When a font becomes "active", the new font appears in the panes whose check boxes are selected. A font becomes active when you double-click its name, or check or uncheck any checkbox.

It would be much better to have a font become active if you just select its name. Alas, that is not possible due to a well-known gap in the Tk.Listbox class.

2. Font sizes must be greater than 0 and less than 100 for them to become in effect.

3. Choosing very _small_ values for font sizes may cause Leo's window to _expand_ its width.

4. When you choose OK or Cancel Leo writes the fonts presently active in each pane to the log pane for your reference. This is useful when setting font options in leoConfig.txt.
This was a supremely irritating bug.
This command switches between vertical and horizontal orientations of the Leo window. In the vertical orientation, the body pane appears below the pane containing the outline and log panes. In the horizontal orientation, the body pane appears to the left the pane containing the outline and log panes.

leoConfig.txt specifies the default direction. If leoConfig.txt does not exist, or does not specify the direction, the "vertical" orientation is used, just as in previous versions.

By default, the ratio of pane outline pane to the body pane is 0.5 in the vertical orientation and 0.3 in the horizontal orientation. These two ratios may be changed using leoConfig.txt.
The @comment directive no longer suppresses syntax coloring. Leo directives (but not doc parts) are colored when @language plain is in effect. The user can, as always, suppress all coloring by using @nocolor. The new rules are much more natural than the old, and much less confusing.
The old colorer colored all Leo directives blue, the same color as keywords in whatever language is current. I think the distinction between Leo directives and language keywords is useful. Of course, you can control syntax coloring yourself by altering leoConfig.txt.
This message is printed to the console, not the log pane. On windows, this may not always be visible.
Leo will convert underscores in the @comment directives to significant spaces. For example,

@comment REM_

causes the comment delimiter to be "REM " (Note the trailing space).
This happened sometimes when using @comment.
Leo directives were mostly being colored using the colors for keywords. This didn't show up because both were colored blue.
Only one window now appears when opening a .leo file, and the window is completely drawn before anything gets written to the log pane.
Added new code for Convert All Blanks and Convert All Tabs.

Fixed a bug that resulted in Convert Tabs being undoable.
This makes the panel much more convenient to use.
leo.py version 2.5.1 July 7, 2002

2.5.1 This version corrects crashers that affect undo/redo move commands. Version 2.5 has been withdrawn. Anyone using 2.5 should use 2.5.1 instead.

This is a major step forward to completing leo.py. It adds significant new features and corrects many bugs. Full details are found in the children of this node. Here are the highlights, in approximate order of importance:

1. Leo supports tab widths properly, and negative tab widths cause Leo to convert tabs to blanks as you type. Three new commands appear in the Edit Body menu: Convert Tabs, Convert All Tabs and Convert All Blanks. Convert All Tabs and Convert All Spaces convert the entire selected tree.

2. Leo now allows you to override selected preferences using a configuration file called leoConfig.txt. Leo acts as before if this file does not exist. An option in leoConfig.txt specifies whether leoConfig.txt is read-only.

3. The Preferences panel is now a modal dialog containing Ok, Cancel and Revert buttons. This makes it clear what preferences are being set.

4. At long last, cut and paste work properly in all situations. You can cut and paste between Leo and other applications, or between two different copies of Leo.

5. Added the Recent Files submenu to the File menu. Very nice to have.

6. Fixed a number of bugs, including several crashers. Fixed several bugs in the Import commands. Leo will no longer abort reading if it detects an invalid directory name in the Default Tangle Directory in the Preferences panel.

leo.py is almost complete. The only remaining tasks on my list are:

1. Make the Set Font... and Set Colors... commands functional. This will be easy now that leoConfig.txt exists. 2. Fix the Javadoc bug.
The following are listed in the approximate order of their importance. The marked nodes are particularly important.
Gary Herron suggested this changes.
These commands convert an entire tree.
This command converts leading tabs to blanks in a single node.
Previous versions of Leo ignored the tab width field! This field is now fully functional.
Leo converts tabs to blanks as you type when a negative tab width is specified.
The Preferences panel is now a modal dialog. This solves a number of problems, both for the implementation and for the user. It is now completely clear which preferences are being set, viz., the preferences for the window on top. In addition, the Prefs panel now shows the name of the window whose preferences are to be set.

The Prefs panel now contains OK, Cancel and Revert buttons. These are useful, and they are possible only because we have a modal dialog.
You may now cut and paste between Leo and other apps, or between two different copies of Leo.
The elements of this submenu are saved in leoConfig.txt, if present.

Leo adds files to this submenu even if leoConfig.txt is missing or read-only, but those additions will not be remembered unless leoConfig.txt is present and writable.
1. Importing the following script

class C: def __init__(self): pass

created an outline with class C appearing in two nodes.

2. Importing the following file created orphan nodes for declaration.

import sys # This line becomes an orphaned node class C: xyz = 'xyz' # This line becomes an orphaned node def __init__(self): pass

3. Importing a Python file containing classes without methods resulted in nested classes. There is still a minor problem: The following

class A: pass

puts the "pass" in a section called << class A declarations >>. This is not easy to fix: you will have to correct it by hand.

4. Improved the Import to @root command. Added @root <filename> to the body text of the root of tree and eliminated @language and @others directives.
This is important, because it means that invalid entries in, say, the Default Tangle Directory preference will no longer abort an attempted read.
This would happen if the Default Tangle Directory did not exist in the Prefs panel.
This command always crashed, perhaps due to a reversion.
The crash happened if the Import command was executed in a new, unsaved file.

The fix was to the directory logic in import.scanAllDirectives.
The bug involves only the clone mark itself: nodes have always been properly joined.
Gary Herron reported this bug and told me how to fix it. This change is to the default file extension field of file dialogs. It seems to have no effect on Windows. Apparently this makes a difference in Linux...
These commands no longer change the file name associated with a window if the user cancels the Save or Save As dialog.
This involved changing the documentation for the Demote, Insert Headline, Extract Headline, Syntax Coloring... and Font... commands. Removed documentation for the Open/Close Log Window command.
leo.py version 2.4 June 20, 2002

This version fixes some annoying bugs and adds some nice features. The children of this node contain full details.

1. Leo now properly highlights the headline of a newly created node. Similarly, the Edit Headline command (control-H) now works. Double-clicking in a headline now does what you would expect: it selects the word under the cursor. Similarly, triple-clicking now selects the entire headline. Also fixes several other minor bugs.

2. Drag and drop. You can now reorganize outlines by dragging nodes around. You must drag from a node's headline and release on another node's headline. See a child of this node for complete details.

3. You can now open .leo files in leo.py by double clicking on .leo files. You must associate leo.py with .leo files. See a child of this node for detailed instructions about how to do this.

4. Improves error recovery when there are errors writing .leo files.

5. Updates this file to match the documentation of Leo's web site. At long last, Leo's documentation is complete.
1. Fixed a bug in app.finishCreate that caused Leo to take an exception on MacOS X.

2. Leo now properly highlights the headline of a newly created node. Similarly, the Edit Headline command (control-H) now works properly.

3. Removed a useless assert in st_enter_root_name that could fail under unusual circumstances.
When writing a .leo file, say f.leo, Leo now attempts to create a backup file called f.leo.back. If there is an error while writing f.leo, Leo will restore f.leo from f.leo.back. Note that Leo will write derived files correctly even when there is an error writing the .leo file.
New in leo.py v2.4: You may drag an node (including all its descendents) from one place to another in an outline. To start a drag, press the main (left) mouse button while the cursor is over the icon for a node. The cursor will change to a hand icon. If you release the mouse button while the hand cursor is above the icon for another node, Leo will move the dragged node after that node. If you release the mouse button when the hand cursor is not over an icon, Leo will print "not moved" and leave the outline pane as it is. This can result in the dragged node not being visible on the screen, but it seems less alarming not to force the current node to become visible as that would undo any scrolling that has just been done.

New in leo.py 3.0: If you drag outside the outline pane, the outline pane will scroll automatically.

Leo scrolls the outline pane as the result of mouse-moved events, so to continue scrolling you must keep moving the mouse. I experimented with scrolling the outline pane whenever the cursor is dragged outside it, but the results were not good. There was a noticeable delay between releasing the cursors and halting the scrolling. The present code is simpler and more likely to work on all platforms.

Leo only allows dragging from one icon to another. In particular, you can't move a node to the first child of another node unless that node is already expanded. I experimented with automatically open nodes when the cursor moves over an unexpanded node with children. However, this was confusing and alarming. It seems best to expand and contract nodes explicitly.
leo.py version 2.3 June 5, 2002

This version fixes a minor problem with Leo.

1. The code that reads and writes @file nodes now uses the directory containing the .leo file as a default when the Default Tangle Directory setting is empty in the Preferences panel. This is a reasonable default because derived files are most often created in the same directory as the .leo file. BTW, the Tangle and Untangle commands have used this convention for a long time.

Note: This default allows us to distribute LeoPy.leo without specifying a directory in the Preferences Panel.
leo.py version 2.2 June 1, 2002

The version fixes two bugs that happen rarely and can cause loss of data when they do happen.

1. In certain circumstances leo.py v2.1 would delete most of an outline when a node was moved in front of the previous root node!

2. All previous versions of leo.py will crash when saving body text containing unicode characters. This could occur as the result of cutting and pasting text from another application into the body pane. Leo.py now writes body text containing unicode characters using Python's u-prefixed notation. That is, the body text is written as: u'escaped_text' where escaped text replaces unicode characters not in the ascii character set by escape sequences of the form \uxxxx. The result contains nothing but ascii characters, so leo.py will have no problem reading it. Naturally, compilers and other tools may not understand Python's notation, so you may have to convert escaped text to something that your tools can understand.

3. This file, LeoDocs.leo, now more closely matches the documentation on Leo's web site.
leo.py version 2.1 May 16, 2002

This version of Leo adds Import and Export commands and fixes several bugs:

1. All Import and Export commands now work. Several of these commands are found only in Leo.py. Some of these commands may be of beta quality.

I use Import commands mainly for studying other people's code. Be wary of generating source files from trees generated by the Import commands.

2. Fixed a bug that caused Leo to crash when a node was moved up in front of the previous root node.

3. Fixed several bugs in the syntax colorer. One of these bugs caused Leo to crash when handling CWEB.

4. Leo now colors strings green.
I use Import commands mainly for studying other people's code. Be wary of generating source files from trees generated by the Import commands.

The Remove Sentinels command can "clean" files for publication or walk-throughs. You can also use the Remove Sentinels command to prepare files for comparisons. For example, one could study a file, say f.py, as follows:

1. Use the Import Files command to create an @file tree, say @file f2.py, for study. Changing the name to f2.py ensures that saving the .leo file won't destroy the original file.

2. Use the Remove Sentinels command to create a file f3.py. Use a file comparison program to ensure that f1.py is identical to f3.py.
Fixed several bugs that caused Leo to hang when moving an outline up before the previous root node.
Fixed a bug that caused Leo to hang when syntax coloring noweb code with the CWEB syntax colorer.

The syntax colorizer now handles Python triple strings and C preprocessor directives properly.

Leo now colors strings with the same green used by IDLE. I think this color looks very good.

Improved the syntax coloring for CWEB.
leo.py version 2.0 March 4, 2002

leo.py v2.0 and Borland Leo v2.6 are being released simultaneously. I urge all users of Leo to upgrade to one or both of these versions. Note: the Untangle commands of both v2.0 and v2.6 should be considered beta quality. Please report any problems with Untangle ASAP.

This is an important milestone in Leo's history, for several reasons:

1. For the first time, leo.py (v2.0) and the Borland Leo (v2.6) are functionally identical. In particular, their respective Tangle commands produce identical output.

2. All "mission critical" aspects of leo.py are now complete: only the Import commands remain unfinished. leo.py v2.0 adds unlimited Undo/Redo, Untangle and full syntax coloring. Unlimited Undo/Redo are reason enough to upgrade to v2.0.

3. leo.py v2.0 and Borland Leo v2.6 introduce an optimized format for derived files: both versions remove blank lines from between sentinel lines. This is an upward compatible change in the format of derived files: leo.py v2.0 and Borland Leo v2.6 can read derived files from all previous versions of Leo, but previous versions of Leo can not read derived files without blanks lines between sentinels.

See the children of this node for full details.
leo.py v2.0 optionally can remove blanks from between sentinel lines in derived files. This is an upward compatible change in the format of derived files: leo.py v2.0 can read derived files from all previous versions of Leo, but previous versions of Leo can not read derived files without blanks lines.

Removing blank lines requires a new kind of sentinel, the "verbatimAfterRef" sentinel. This allows Leo to handle comments following section references that would otherwise be treated as sentinels themselves. For example:

<< section >> #+others

Leo removes blanks lines between sentinels by default. To cause Leo not to remove blank lines do the following. In the section called << initialize atFile ivars >> in leoAtFile.py, change:

self.suppress_newlines = true to self.suppress_newlines = false

Note: leo.py v2.0 can read derived files without blank lines between sentinels regardless of the setting of self.suppress_newlines.
leo.py supports unlimited Undo and Redo. All outline operations and all typing operations may be undone and redone. Undo state persists even after Saves. Even the Change All command is undoable. Only the "Read @file Nodes" command is undoable; it clears the entire undo state. This command raises a dialog to allow the user to cancel.

Think of the actions that may be Undone or Redone as a string of beads. A "bead pointer" points to the present bead. Performing an operation creates a new bead after the present bead and removes all following beads. Undoing an operation moves the bead pointer backwards; redoing an operation moves the bead pointer forwards. The Undo command is disabled when the bead pointer moves in front of the first bead; the Redo command is disabled when the bead pointer points to the last bead.

leo.py syntax colors all languages mentioned in the Preferences Panel.
1. Show Invisibles command works in @nocolor mode.

2. @unit, @root, @nocolor and @color now terminate doc parts.

3. Three characters were colored blue at the start of a section def in:

<<name>>=

This didn't show up if there is a space after the <<.
Leo adds extra space at the end of headlines so that headlines now have enough room to show all text. This works around an apparent Tk bug.
The Untangle command is beta quality. Please make backups before untangling.
leo.py v2.0 fixes two bugs present in Borland v2.5 and fixed in Borland v2.6:

1. Untangle did not work properly for languages like Perl and Python that do not have block comments.

2. Untangle did not recognize @ on a line by itself as the start of a doc part.
@c now works the same as @code. This makes the syntax colorers agree with what the Tangle commands actually do. Previously, the syntax colorers indicated that everything following an @c was code, while the Tangle commands placed everything following an @c in the doc part!
Previous versions of untangle silently trimmed trailing lines from all nodes, which seems rude.
The font size for code text is now 12 on Linux.

Changed code so that the path to the Icons folder is computed correctly on all platforms.
leo.py version 1.0 February 10, 2002

Version 1.0 adds many new features and fixes several bugs. I recommend that all users of leo.py upgrade to this version. This version deserves the number 1.0; it has no known bugs.

Here are the highlights of this release. See the children of this node for details.

1. Fixed bugs: the Tangle All and Tangle Marked commands didn't work at all. Fixed several bugs in the Find and Change commands. The "Suboutline Only" option now applies to the Search All and Change All commands. Settings in the Preferences Panel now "stick" properly to the currently selected window. Fixed many minor bugs.

2. leo.py now re-marks @file trees as dirty if errors are found while writing those trees. This makes it difficult to write derived files that will be out-of-synch when read again.

3. New commands and features: Added the Revert, Select All, Delete, Sort Children, Sort Siblings, Contract Parent (very useful!) and Toggle Active Pane commands, and all commands in the Edit Body menu, and all the Go To commands in the Move/Select menu. The Show Invisibles commands is ugly, and useful nonetheless.

The Edit Body commands work much more reliably than the similar commands in the Borland version of Leo. You may now specify Python scripts rather than .bat files in the Preferences panel. Added a way, albeit clumsy, to cut and paste nodes between Borland Leo and leo.py. When syntax coloring Python text, leo.py increments the auto-indentation by one tab after a trailing colon. This provides a subtle reminder to add such colons.

4. leo.py handles events and shortcuts properly. The shortcut for the Insert Node command is now Control-I, as it is in the Borland version of Leo. Worked around a TK bug that prevented double-clicks from properly selecting words. leo.py enables and disables all menu items properly.
Removed the Print and Page setup commands because Tk provides no support for printing. This is not significant: we can use the export commands to create text files. These text files can then be formatted and printed as desired.

The following commands remain unfinished. They will be finished in this order:

1. Untangle commands.

2. Import/Export commands.

3. Unlimited Undo and Redo.

4. Toggle Split Direction, Cascade and Recent Windows commands.

5. Font Panel and Syntax Coloring commands.
It should be possible to cut a node or tree in Borland Leo and paste it using the Copy Node command in leo.py. At present, cutting and pasting trees between applications can only be done indirectly. Do this:

1. Copy the tree in Borland Leo using the Copy Node or Cut Node command. 2. Paste the tree into empty body text of leo.py using the Paste command from the Edit menu, _not_ the Paste Node command from the Outline menu. 3. Select all the body text and choose Copy or Cut from the Edit menu. 4. Move to the tree view, and choose Paste Node.

I hope to make this unpleasantness go away soon.
1. Fixed the Tangle All and Tangle Marked commands. They always crashed.

2. The Change command did not work in headlines because of a reversion in how headline text is selected.

3. All event handlers now return "break". This prevents further event handling and allows leo.py to handle events properly. This cleared up the last major mystery in leo.py.

4. Settings in the Prefs Panel now "stick" to windows. That is, they change when the user selects a new Leo window. In particular, the Default Tangle Directory is restored properly. This is important, as otherwise the Tangle commands might write files to the wrong place.

5. Removed calls to trace during shutdown. In general, it is not possible to call app() during shutdown because the app object may no longer exist.

6. @others was not syntax colored properly if it was indented.
1. Adding trailing whitespace to a line in body text does not set the file-dirty mark. This can never cause a derived file to become "out-of-synch" because the read code does not compare body text.

Apparently there is no way to fix this bug because of holes in Tk's event mechanism. Specifically, tree.idle_body_key has no way to tell directly what keystroke caused it to be entered.

2. Cutting and pasting between applications can only be done indirectly. See the node called "How to copy an outline from Borland Leo to Leo.py".
leo.py version 0.08 February 3, 2002

Version 0.08 adds important new features and fixes several bugs. All users of version 0.07 should upgrade to this version. This code seems solid, and significant bugs may remain. Note: this code has been tested only on Windows XP. Please report any problems with Linux or Macintosh.

This is the first version of Leo to feel as smooth as the Borland v2.5. leo.py is now less buggy than v2.5; the Borland version will soon be upgraded to match leo.py.

Here is a summary of new features. See the children of this node for full details.

1. The Find and Change commands are fully functional. These commands support Tk's "advanced regular expressions" when the "pattern match" option is checked. Tk's regular expressions are documented at:

http://tcl.activestate.com/man/tcl8.4/TclCmd/regexp.htm

2. The Tangle commands work; leo.py now supports @root trees. The next release will support the Untangle commands. Converting the Untangle code to Python is almost complete.

3. Leo now ensures that the currently selected headline is always visible, scrolling the outline pane as needed.

4. Improved Leo's memory management and eliminated several serious bugs. One bug caused errors while reading @file trees. Eliminating this is a huge step forward. This bug exists in the Borland version of Leo and will be fixed soon.

5. Leo now underlines undefined section references. Added support for hyperlinking, but disabled hyperlinking because it is not very useful without browser navigation buttons.
leo.py handles Find and Change commands much more smoothly than does the Borland version of Leo. Indeed, I plan to rewrite the Borland code to match the code in leo.py.

In general, leo.py handles the Find and Change commands more smoothly than does the Borland version of Leo. Note: the node that "anchors" wrapped searches is the current node when any change is made to the Find panel. The anchor is recomputed after a wrapped search fails.

Changed the meaning of a Find panel setting from "batch" to "context". This will be changed in a future Borland version. In the meantime, setting the "context" option in leo.py will set the "batch" option in the Borland version.

The "context" option affects only the "Find All" and "Change All" commands. When this option is in effect, more context information is printed when matches are found.

The "reverse" option affects all Searches and Changes, both those initiated from the Find Panel itself, and those initiated from menu commands. In particular, F3 will find the previous search if the "reverse" option is in effect.
The Tangle command has passed significant tests, and bugs may remain. Be warned.

leo.py's Tangle command produces output that is slightly different from Leo v2.5. The differences are insignificant; here is the complete list:

1. The header line, i.e., the first comment line of the tangled file: leo.py produces forward slashes in the path name, Leo v2.5 produces back slashes.

2. Leading whitespace: leo.py produces "optimized" leading whitespace. leo.py removes blanks if they do not affect the visual appearance of whitespace; this depends on the tab width specified in the Preferences Panel. Leo v2.5 copies leading whitespace exactly. The leo.py way is preferable; it will produce output acceptable to Python's tabnanny regardless of tab width used while running Python.

3. Trailing blanks lines in body text: leo.py removes all trailing whitespace. Leo v2.5 does not. Conceivably, leo.py might have to change to make the Untangle command work properly. More likely the leo.py way is just better.
Underlining "live" links clutters the screen too much, and hyperlinks from section references to section definitions isn't very useful without the standard "back" and "forward" buttons typically found in web browsers. For these reasons, I have disabled hyperlinking.

To enable hyperlinking, just set:

self.use_hyperlinks = true

in color.__init__ in the @file leoColor.py tree.
1. Fixed several bugs involving cloned headlines. One bug caused cloned headlines not to be updated in synch. Another bug caused @file trees not to be marked as dirty under certain conditions.

Both bugs could have caused read errors while reading @file trees, so fixing these bugs is a big step forward. I have experienced no read errors since fixing these bugs. A huge step forward. This bug exists in the Borland versions of Leo and should be fixed ASAP.

2. There was a big performance bug lurking in the tree code that became apparent during testing of the Find and Change commands. Closing a window could take almost a minute if the tree had been redrawn many times. Old widgets were never deleted until the window was closed. This may be a problem with tkinter, and the workaround was straightforward.

3. Fixed a bug in the "Save To" command. After doing a Save To, a following Save command used the Save To file name! This explains why some saves "didn't take".

4. Fixed a bug in the Save commands. Cancelling a Save dialog did not work: instead it wrote a file called ".leo". I am not sure whether this bug occurred in previous releases.

5. All Save commands now always recompute file indices. This old logic accomplished very little, and introduced needless differences between the various kinds of Save commands.

6. Made file and path name processing more portable. Removed all calls to os.chdir().

7. Fixed a bug that caused a control-d to delete a character in the body pane when the Move Down command didn't do anything, i.e., when the presently selected node was the bottom node on the screen.
1. Used Tk.Text widgets in headlines rather than Tk.Entry widgets. This allows the Find/Change commands to search headlines. Control-k no longer deletes text following the cursor in headlines.

2. Improved the performance of screen updates and markedly reduced flicker in the body pane.

3. Found a way to handle keystrokes without knowing the status of the control and alt keys. The idle event handlers now ignore the actual keystrokes in most cases, and take action solely based on whether the headline or body text has actually changed as the result of those keystrokes. If not, we can assume that a command has been entered and we do not set the dirty or changed bits.

4. Replaced es_newline global with app().log.es_newline, i.e., frame.es_newline. This is better than before because each log stream now has its own newline count.

5. Reduced width of the log pane to give more space to the tree pane.

6. The LeoPy folder now contains a dummy file called __init__.py. This file marks the LeoPy folder as a potential package.

7. Added several new functions to the file leo.py. These are useful for invoking leo.py from a Python interpreter. See the section "How to Install & Run Leo" for details.
Previous version of leo.py did not release memory when windows close. This version does.

Memory management is remarkably easy in Python/Tk. Added __del__ routines and corresponding destroy routines to the frame, vnode and tree classes. The destroy routines eliminate circular references between objects. The __del__ routines do nothing except print debugging traces; we know that all references to an object have been removed when its __del__ routine is called.

Added app.deleteOnClose ivar to control memory management. When true, Leo deletes all frame objects when a frame closes. This is always true at present.

Fixed a memory botch: previously a dummy vnode was allocated for every vnode created while reading files.
Starting with this release, you will see trace statements throughout the code. The trace function is defined in leoUtils.py; trace implements much of the functionality of my Sherlock tracing package. Traces are more convenient than print statements for two reasons: 1) you don't need explicit trace names and 2) you can disable them without recompiling.

In the following examples, suppose that the call to trace appears in function f.

trace(string) prints string if tracing for f has been enabled. For example, the following statement prints from s[i] to the end of the line if tracing for f has been enabled.

j = skip_line(s,i) ; trace(s[i:j])

trace(function) executes the function if tracing for f has been enabled. For example,

trace(self.f2)

You enable and disable tracing by calling init_trace(args). Examples:

init_trace("+*") # enable all traces init_trace("+a","+b") # enable traces for a and b init_trace(("+a","+b")) # enable traces for a and b init_trace("-a") # disable tracing for a traces = init_trace("?") # return the list of enabled traces

If two arguments are supplied to trace, the first argument is the "tracepoint name" and the second argument is the "tracepoint action" as shown in the examples above. If tracing for the tracepoint name is enabled, the tracepoint action is printed (if it is a string) or executed (if it is a function name).

"*" will not match an explicit tracepoint name that starts with a minus sign. For example,

trace("-nocolor", self.disable_color)
Rewrote the code that computes file names. This should have two beneficial effects:

Note: This code has only been tested on Windows XP.

1. The code should be more portable. I've used os.path.join throughout to concatenate parts of filenames, and this should handle differences involving path separators on different platforms.

2. The code no longer calls os.chdir(). The "current application directory" changes only as the result of completing file open and save dialogs. In particular, @path, @root and @file do not change the current application directory.

3. Used leo.__file__ to get the directory from which leo.py was loaded. This seems to be reliable, though it has only been tested on Windows XP.
The following features of leo.py will be incorporated into the Borland version 2.6 of Leo:

1. Improve leading whitespace handling in Tangle.

2. Fix clone bugs (rewrite vnode::setDirty). These bugs can cause errors while reading @file nodes.

3. Improve Find/Change commands. Change "batch" to "context". This will make the Preferences Panel work identically in both programs.

4. Always recompute file indices and compute file indices. This will make derived files produced by v2.6 identical to the files produced by leo.py.

5. Make sure all file names end in .leo.
I urge anyone using leo.py upgrade to this version. This is the first version of leo.py that has been thoroughly tested.

See the children of this node details about this release. Here are the highlights:

1. Fixed many bugs, including a bug that can cause body text to be lost when creating a new headline.

2. The Python window now works. You can now run Python scripts from within leo.py!

3. Added a script to convert Leo files from Leo 1 to Leo 2 format.

4. @nocolor and @color are now inherited properly from ancestor nodes.

5. Added Leo Documentation to Help menu. This command opens LeoDocs.leo.

6. Added auto indent to body pane.

For even more details, see the diary entries for January 6-9 in LeoPy.leo.

See the To Do lists in LeoPy.leo for a list of unfinished features. The primary unfinished features are the Find/Change commands, preferences settings and the Tangle/Untangle commands.
This is the first version of leo.py that has been heavily tested. About 70 percent of my work on leo.py has been done using leo.py itself. I know of no remaining bugs that can cause data to be lost.
I have concentrated on removing all significant irritations encountered while using leo.py. Far fewer still remain than before. Among the remaining irritations:

1. Syntax coloring happens at "idle time", that is, after all events have been handled. This means that characters are initially colored black, then changed to another couple after an obvious delay. The obvious event-handling code does not work. Perhaps using a "virtual event" may work.

2. Keystrokes that should initiate commands _also_ get passed to widgets. I am hoping to find a way around this, and it may take a bit of work. The two biggest offender: Control-k while editing a headline deletes from the cursor to the end of the headline (in addition to creating a new node!) Control-i gets passed to the body pane as a tab.

3. The border around the tree pane gets over-written by headlines. I'm not sure how to fix this: it is certainly a Tk bug.
Fixed a bug that can cause body text to be lost when creating a new node.
Choosing Open Python Window brings up IDLE, the standard Python IDE, from which scripts may be run and debugged.

Note: The path to idle.py must exist in sys.paths. For Python2.1 on windows, this path will typically be \Python21\Tools\idle If this path does not exist, a message will be sent to the log pane. The usual way to add a path to sys.paths is to modify the sitecustomize.py file.

Changed names of several vnode and commands methods to make them compatible with the scripting documentation on Leo's web site. You may now run any script that runs on Borland Leo on leo.py.

When running scripts from leo.py, you may refer to any method or instance variable (attribute) in the code for leo.py. If you want to run those scripts on Borland Leo you should restrict yourself to the methods documented in the Scripting chapter of Leo's online documentation.
To convert an @root tree to an @file tree, choose the root of the tree to be converted, then do the following in the Python window:

import c2py c2py.leo1to2()

This script makes numerous changes throughout the tree. It does not, however, change @root to @file, or insert the needed @others directives. You must do that by hand.
At long last it is possible to open this file, LeoDocs.leo, from the help menu.

This command will work only if LeoDocs.leo is located in the directory in which leo.py exists.
The body pane now has auto-indent on at all times.
leo.py version 0.06 (alpha) December 20, 2001

This version fixes several minor problems that prevented leo.py from running on Linux. There is no need to upgrade to this version if leo.py works on your system. This version is neither more nor less stable than version 0.05. This file, LeoDocs.leo, has also been improved substantially.

There still may be problems running leo.py on Linux. You may be able to fix those problems yourself after reading this file... I'll be on vacation until about January 8. I may be able to answer questions on Leo's SourceForge site, and I'll make no code changes until then.

Problems fixed:

1. Removed call to print_stack() in es() logic. If there are problems during initialization this logic printed "Null log" and then printed a stack dump. This dump was more confusing than helpful.

2. Changed "darkblue" to "DarkBlue". The standard colors known to Tk are listed at http://tcl.activestate.com/man/tcl8.4/TkCmd/colors.htm

If you have more problems with unknown colors, please use a color listed above.

3. Eliminated some accelerators (Linux only). You may take an exception in createAccelerators() in leoFrame.py if Linux does not support the accelerators defined in one of the following sections:

<< control key bindings >> << alt key bindings >>

Just comment out the relevant entry. Please let me know which accelerators don't work. I can add platform-specific code to deal with them.

4. Added loaddir code to leo.py. Leo tries to guess where leo.py was loaded from. If this doesn't work leo may take an exception in leo.py.

As a temporary hack, please set loaddir correctly in the following code at the start of leo.py:

# loaddir should be the directory that contains leo.py if sys.platform=="win32": loaddir = "c:\\prog\\LeoPy\\" else: loaddir = "LeoPy"

5. Removed bullet 0x95 characters from this file. They don't show up well on Linux.

6. In leoTree.py, changed the filenames from x.gif to x.GIF. Linux is more picky about case in filenames than Windows.

Edward K. Ream December 20, 2001
leo.py version 0.05 (alpha) December 17, 2001

This version of Leo marks a major milestone in Leo's history. leo.py is the first functional version of Leo written completely in Python and Tkinter, Python's interface to the tcl/Tk system.

leo.py is quite useable. leo.py should work on any machine with recent Python and tcl/Tk interpreters. This version of Leo was created using Windows XP, Python 2.1 and tcl/Tk 8.3.

Warning: This version is alpha quality. Use extreme caution: please save your work often and make full backups of all .leo files and derived files.

Please read this node's children before sending bug reports or feature requests. Send all bug reports to Leo's SourceForge web site at: http://sourceforge.net/projects/leo/

Edward K. Ream
I have been happily using leo.py to develop itself for the last three days without serious problems. leo.py reads and writes all its source files exactly as does the Borland version. This is an important test, and significant errors may remain.

Performance on a 1.2 Ghz Pentium is good to excellent. Drawing the screen happens quickly, smoothly and without flicker. Expanding and contracting nodes is usually excellent, and slows when a many nodes have been expanded. Syntax coloring appears instantaneous, even with an unsophisticated algorithm. Loading LeoPy.leo files takes about 3 seconds. This could be improved by using Python's XML classes.
You must have recent versions of Python and tcl/Tk installed on your machine. Download Python from http://python.org/ Download tcl/Tk from http://tcl.activestate.com/software/tcltk/

After installing Python and Tk, run Leo double clicking the file leo.py in the LeoPy folder. On Windows this will bring up a Leo window and a black Python interpreter window.

The LeoPy folder contains all files in this distribution. leo.py is the main source file; run Leo by double clicking leo.py. The Python interpreter will create corresponding .pyc files as .py files are imported by leo.py.

LeoPy.leo is the master source code file; all .py files were derived from LeoPy.leo. c2py.py and leoCompare.py are tools that should be run from a Python interpreter.
Functional Commands and Features

Open, Save, Save As, Save To commands. Read @file Nodes command. Read Outline Only command. Write Outline Only command. Warning: this has not been tested thoroughly. Insert Node, Delete Node and Clone commands. Move Node Right/Left/Up/Down commands. Cut Node, Copy Node, Paste Node commands. Syntax coloring for Python only. The @color and @nocolor directives, but these are not inherited yet.
Unfinished Commands and Features:

Revert. All Tangle and Untangle commands. All Import commands. All Find and Change commands. Sort Children command. Toggle Split Direction, Toggle Active Pane and Cascade commands. Auto indent in the body pane. The Preferences panel is semi-functional: see note one below. The Find panel is non-functional. The Python scripting window is non-functional. Syntax Coloring Options and Font Options windows are not ready yet. @color and @nocolor directives are not inherited yet.

Notes:

1. The Preferences panel is partly functional. The default target language option has no effect: syntax coloring is for Python only. Tab width can not be changed; tabs are 8 characters wide.

2. Menu items are not yet dynamically dimmed and undimmed. Some non-functional commands have undimmed menus. Keyboard accelerators work even for dimmed menus.

3. Scripting Leo is not possible yet. You can't control Leo from the Python interpreter because control passes to the Tk event loop.

4. Leo's Save commands do not yet save the information about which node was visible at the top of the tree.

5. The Save/SaveAs/SaveTo dialogs don't handle shortcuts to folders properly; they try to replace the shortcut with the .leo file.

6. Initialization code in leo.py computes the directory from which Leo was loaded using argv[0]. I am not sure this is proper, though I have had no problems recently. Tree icons will not load properly if argv[0] does not specify this directory. If the path is invalid Leo will print: os.chdir failed: `dir`

7. The tree class does not automatically ensure that the current node is visible after an outline operation.

8. The font used in the body pane is not great. I hope to improve it.

9. Headline text is sometimes obscured.
leo.py already feels less buggy than the wxWindows versions of Leo. Rewriting Leo in Python and Tkinter has greatly improved the code. Further improvements are possible. BTW, work began in earnest on this version of Leo in late October, 2001. It's been a great month.

The following problems have resisted my initial attempts at solving them. Any help from Tk experts would be appreciated:

1. Window icons are the Tk icon, not the nifty Leo icon. Really, there _must_ be a way to do this!

2. Can variable-width tabs can be done in Tk? If not, Leo may have to convert tabs to blanks as specified by the preferences panel.

3. Cut, copy and paste work in the log and body panes using Control-X, Control-C and Control-V keys. However, there are no Cut, Copy or Paste menu items in the Edit menu. Is it possible to send simulated keystrokes to a text widget?

4. Some menu accelerators do not seem to be available in Tk. In particular, many of the items in the submenus of the Outline menu have no accelerators yet. These commands are useful only if they can be chosen from the keyboard rather than the menu.

5. The border of the tree control is gray. It can be overwritten by large headlines. This appears to be a Tk or Tkinter bug; I haven't found any way to eliminate the border or color it white.

6. There doesn't seem to be a way to select all the headline text when editing starts. The click that starts editing always clears the selection.

7. Changing the type of leo.py to .pyw doesn't seem to work.

8. A message flash briefly on the screen when leo.py exits, too fast to read. These messages do not appear when invoking leo.py from the Python interpreter. They probably would be visible in a Linux window.
It will be much easier to make such improvements to leo.py than to previous versions of Leo. Here is my list of new things to do:

1. Create "hyperlinks" from section references to section definitions. This will happen in the next release.

2. Add automatic "tab nanny" checking and full syntax checking of Python body text.

3. Build a file/directory comparison window using code in leoCompare.py.

4. Build a Python debugger into Leo. Python provides excellent hooks for debugging and tracing. Python's stand-alone IDLE debugger doesn't seem to work well with the Tk command loop.

5. Expand Leo's file format to take advantage of the power of Tk text widgets.
Leo Borland version 2.6 May 15, 2002

This version fixes a bug that can cause errors while reading derived files. Earlier versions do not ensure that clone indices in derived files are distinct.

I recommend that all users of the Windows version of Leo upgrade to this version.
Leo Borland version 2.6 March 4, 2002

leo.py v2.0 and Borland Leo v2.6 are being released simultaneously. I urge all users of Leo to upgrade to one or both of these versions. This is an important milestone in Leo's history, For the first time, leo.py (v2.0) and the Borland Leo (v2.6) are functionally identical. In particular, their respective Tangle commands produce identical output.

Borland version 2.6 fixes major problems and contains numerous changes to make it fully compatible with leo.py v2.0. Highlights of Borland v2.6:

1. Fixes bugs that caused spurious errors while reading @file trees. Such errors can destroy clone links.

2. Supports an improved format for derived files. This format is used by leo.py 2.0. This is an upward compatible change: previous versions of Leo can not read the new format, and this version of Leo can read all previous derived files.

3. Fixes two long-standing bugs in the Untangle command.

4. As mentioned above, the Tangle commands are byte-for-byte compatible with leo.py v2.0. Also, @c is now equivalent to @code.

5. Adds two very useful commands: Sort Siblings and Contract Parent.

See the Children of this node for full details.
This version of Leo removes blanks from between sentinel lines in derived files. This version of Leo is fully compatible with leo.py v2.0 in this regard.

This is an upward compatible change in the format of derived files: Borland Leo v2.6 can read derived files from all previous versions of Leo, but previous versions of Leo can not read derived files without blanks lines.

Removing blank lines requires a new kind of sentinel, the "verbatimAfterRef" sentinel. This allows Leo to handle comments following section references that would otherwise be treated as sentinels themselves. For example:

<< section >> #+others
Whenever a node changes, Leo now ensures that all nodes cloned to it, and all ancestor @file nodes are marked dirty. This is the cause of spurious read errors.

As a side effect, nodes now become dirty when they move.

This change was made in leo.py v0.8.

It is also vital that @file nodes remain dirty until they are written without errors. Otherwise, a Save command will succeed without warning, and a later read may cause the dreaded "clones have been cleared" message.

This change was made in leo.py v1.0.
@c now works the same as @code. This makes the syntax colorers agree with what the Tangle commands actually do. Previously, the syntax colorers indicated that everything following an @c was code, while the Tangle commands placed everything following an @c in the doc part!
This version fixes two long-standing bugs:

1. Untangle did not work properly for languages like Perl and Python that do not have block comments.

2. Untangle did not recognize @ on a line by itself as the start of a doc part.
The Tangle commands are now byte-for-byte compatible with leo.py 2.0. Tangle outputs "optimized" leading whitespace just as leo.py does. This can be important in Python programs.

All the Save commands now recompute clone indices just as in leo.py 2.0.
The "Show Context" checkbox in the Find Panel is presently non-functional. (In leo.py it affects the messages produced by the Find All and Change All commands.)
Unlike leo.py, the Borland version of Leo can not ensure that saved outlines have .leo extensions.

There are two problems:

1. The ensureExtension routine is called after the dialog closes. If this routine changes the file name we could overwrite an existing file!

2. There appears to be problems with the Borland DefaultExt field. Setting this field causes crashes.
This version fixes several serious bugs. All users of Leo2 should upgrade to version 2.5 immediately.

1. Eliminated the ill-fated automatic updating feature. This turns out to be very dangerous and disconcerting. If one makes changes to the .leo file and does not save those changes, a later update could erase those unsaved changes!

2. Removing automatic updating also fixed another bug: The current outline node was not being selected properly when a .leo file was read, so that in some cases the body text did not correspond to the selected outline node. This never caused loss of data, and was disconcerting.

3. Fixed a bug that caused Find/Change settings to be saved to a file only if a find or change command was actually executed.

4. Restored several parts of Leo2(cb).leo that were inadvertently erased due to earlier bugs. In particular, the outlines for xbuf.c and xbuf.h have now been restored.

One bug remains. On Windows XP, the cursor is obscured in the Find/Change dialog when it is at the far left of the Find or Change text areas. This may be a botch in the XP text control itself. The solution would be to create a border in the text areas, and there is no obvious way to do this using the Borland classes.
Version 2.4 is a major improvement in Leo2. I urge all users of Leo2 to upgrade to this version. See the children of this node for full details.

The "old" Leo2 tried to save space by not writing all information in @file trees. This was a serious mistake. The "new" Leo2 saves all information in an outline to the .leo file, including all information in @file trees. Once again you may back up a project merely by backing up the project's .leo file. There is no Archive command, and no need for one. All .leo files are now safe archives.

As the result of the change in file formats, Leo 2.4 may clear clone links when loading derived files created with previous versions of Leo. Leo 2.4 will issue detailed messages when that happens.

The new Leo2 is much safer and more convenient to use than the old Leo2. Error messages and error recovery have been improved.

Leo2 updates @file trees automatically when Leo2 is activated. For example, suppose you change a derived file in another editor, say in an IDE. Leo2 will update the @file tree when you switch to Leo, provided that you have saved the file in the IDE. Warning: due to several apparent bugs in the Delphi code, this feature only works if the main Leo window is at least partially visible on the screen (!)
Version 2.4 fixes several bugs involving clones.

1. Version 2.4 rewrites clone indices when writing derived filed, so node sentinels accurately reflect the clone status of the node. The node sentinel for the root node no longer contains the maximum clone index. Leo2 clears the root's clone index on reading and writing so that the root node is not mistakenly joined so another node!

2. Version 2.4 takes more care to recreate clone links properly when reading derived files. A derived file can be read into a .leo file different from the .leo that wrote the derived file, so Leo2 makes sure that clone links are proper in the .leo file being opened. A minor drawback: .leo files now are "big", so reading them is a little slower.

Note: Clone links into @file trees work only if the .leo file is "in synch" with the files derived from the @file trees. Clone links will break if the .leo file and derived files are not in synch. Typically "backup" .leo files will be out-of-synch with present derived files, so it's best to use "backup" .leo files only in emergencies, for example to revert a project to a previous state.
The "old" Leo2 tried to save space by not writing all information in @file trees. This was a serious mistake. The "new" Leo2 saves all information in an outline to the .leo file, including all information in @file trees. It is once again possible to back up a project merely by backing up the project's .leo file.
The archive command was a backup disaster waiting to happen. It has been replaced by the following four file commands are located in the File:Read/Write submenu.

Read Outline Only: reads an outline using only the .leo file, not any files derived from @file nodes. This command is useful for reverting a project to a previously saved state.

Read @file Nodes: updates all @file nodes in an outline. This ensures that the state of an outline matches all files derived from @file nodes. Included for completeness. This happens automatically when a .leo file is first read, and it also happens automatically when Leo2 is reactivated.

Write Outline Only: saves an outline without writing any @file trees. Useful for inserting an @file node into an outline without modifying a derived file with the same name.

Write @file Nodes: forces an update of all @file trees. Surprisingly useful.
Leo2 updates @file trees automatically when Leo2 is activated. For example, suppose you change a derived file in another editor, say in an IDE. Leo2 will update the @file tree when you switch to Leo, provided that you have saved the file in the IDE.

Warning: due to several apparent bugs in the Delphi code, this feature only works if the main Leo window is at least partially visible on the screen (!)
Revised for version 2.4

Loading and saving .leo files usually keeps the .leo file and derived files "in synch". However, if you create backup .leo files, it can easily happen that the structure of @file nodes in the backup .leo files no longer matches that structure in the derive files. Leo2 will detect this condition the next time you next open the old .leo file. When that happens, Leo2 discards the entire out-of-synch @file tree, and recreates it solely using the derived file. In the process all marks and clone links from outside the file are lost. Leo2 issues the messages:

Error reading @file <filename> -- Rereading file. Clone links into this file will be lost.

to remind you that the .leo file you are using is out-of-date. Saving the .leo file will bring it up to date.

If one or more sentinel lines in a derived file have been corrupted in some way, by inserting, deleting, altering or moving a sentinel, Leo issues these messages:

Error reading @file <filename> <An error message about a bad sentinel> -- Rereading file. Clone links into this file will be lost. <An error message about a bad sentinel> (again) ----- File may have damaged sentinels!

Data may not actually be lost, and the outline structure will be seriously damaged. The most sensible thing to do is to restore the derived file from a .leo file using the Read Outline Only command.

No error recovery is attempted when automatically updating @file trees. If an error is found the update is canceled (leaving the outline unchanged) and the following message is given:

-- Update canceled due to structure errors

We don't attempt error recovery because error recovery would clear clone links. For example, suppose we are editing a .leo file, and for some reason we also edit a derived file _without_ saving the .leo file. Now the files could be out of synch! So it is best not to press on with an automatic update.
Revised for version 2.4

If Leo2 detects orphan or ignored nodes while writing an @file node it saves all information in the @file tree in the outline (.leo) file. No information is lost. When the .leo file is read again Leo2 will load the information in the @file tree from the .leo file, not the derived file.

As a reminder, Leo2 marks the erroneous @file node as dirty so that saving the .leo file will attempt to rewrite the @file node. Version 2.4 no longer marks the @file node as "stuck on dirty". If you save the .leo file the dirty mark will be cleared. However, the @file node will be dirty every time the file is read until all errors are corrected.
@file trees are less flexible than @root trees:

1. You can not use section definitions in more than one file. For example, you can't define declarations in one file to be used in another file. If you need all the features of noweb you must use @root trees.

2. @file trees use no escape conventions whatever. A code part in an @file tree contains a section reference if << precedes >> on the line, regardless of whether << or >> are contained in strings or comments. In practice, such "false references" occur infrequently, and the workaround is simply to split the offending line. Moreover, noweb's escape conventions (@<< and @>>) cause problems in @root trees, so this "drawback" is very minor.

3. Clone links into @file trees work only if the .leo file is "in synch" with the files derived from the @file trees. In particular, backup .leo files will usually be out of synch with derived files. Clone links will break otherwise. To recover from such errors, simply use the "Read Outline Only" command.
Note: 10/19/01: I recommend that all users upgrade to version 2.4. There are some serious problems with version 2.3.

This version of Leo contains some significant improvements. See the children of this node for details:

1. The new @first directive allows you to place lines at the very start of files derived from @file nodes. For example, @first #!/usr/bin/env python

2. The log window is now part of the Leo window. Very handy.

3. Fixed several bugs involving the Find command.

4. A new Archive command saves an entire outline (including all @file trees) to a .leo file. Use this command with caution. Loading and then saving a .leo file created with the Archive command will revert an entire project to the state it had when the archived .leo file was created.
The @first directive allows you to place lines at the very start of files derived from @file nodes. For example, the body text of @file spam.py might be:

@first #! /usr/bin/env python

The body text of @file foo.perl might be:

@first #/usr/bin/perl

@first directives are recognized only at the start of the body text of @file nodes. No text may precede @first directives. More than one @first directive may exist, like this:

@first #! /usr/bin/env python @first # more comments.
The Archive command writes all nodes of an outline to a .leo file, including all parts of @file trees. All @file trees are marked in such a way that when the archived .leo file is read 1) all @file trees will be restored from the archived outline rather from derived files and 2) all @file trees will be marked dirty so that when the archived .leo file is written all @file nodes will be updated.

Use the Archive command (and the files written by it) with caution: loading and then saving an archived .leo file will back up an entire project to its state when the archive was originally written!
Added support for @c in syntax colorers for Python, Pascal, Perl, Java and HTML.
Fixed a bug in the Change, Then Find command. The Change, Then Find command would rescan the newly changed text if the "change text" contained the "find text" preceded by other text of length greater or equal to the length of the original "find text".

For example, changing "blah" to "const blah" would result in rescanning the newly inserted "blah". This would cause the "Change All" command to hang.

The old code advanced the search point by the length of the "find text." The new code advances the insertion point by the length of the "change text".

Fixed the Find/Change panel: some captions were cut off.
@file nodes are now marked dirty whenever any change is made in the @file tree, including changes made as the result of changing cloned nodes outside the @file tree.
Made the log window part of the Leo window. Having the log be part of the leo window is very convenient.
Leo2 readme file Borland version 2.2, 9/16/01

This version fixes several bugs in Leo2:

1. Fixed a bug that causes all data in an @file tree to be lost under the following conditions:

A. The @file tree was erroneous, i.e., it contained orphan or ignored nodes. B. The .leo file containing the erroneous tree was saved twice.

Leo2 now marks an erroneous @file node as "stuck on dirty" until the tree is fixed. Recall that dirty nodes have icons with a black border rather than a gray border. Whenever a file Save operation occurs, Leo2 will attempt to write all dirty @file trees, thereby reminding you that errors remain. However, the .leo file itself is not "stuck on unsaved", so you won't be prompted to save the .leo file endlessly.

While fixing this bug I also improved error and warning messages relating to reading and writing @file nodes. This should make it clear that a) data in erroneous @file trees is saved to the .leo file and b) data is recovered from the .leo file when reading erroneous @file nodes.

2. Fixed a bug relating to clones that could cause loss of data. Leo2 now marks all clones dirty when any clone changes, and makes sure to mark @file nodes dirty that contain changed clones.

3. Changed the code that writes @file trees to end lines with "\r\n" rather than "\n". I hope this will improve matters, though I have my doubts. Please report any problems with the new scheme immediately.

4. Added Write @file Nodes command. The command writes all @file nodes in the selected tree, regardless of whether they have been changed.

5. Made the commands in the Main Leo2 window functional.
Version 2.1 is the first public release of Leo2. This version appears stable, and should be used with caution.

The major feature of Leo2 is that tangling and untangling happen automatically for all code derived from @file nodes.

Remember to back up all files derived from @file nodes; such files are original source files! It is no longer possible to back up an entire project by backing up a single .leo file.
Version 1.15 can read @file trees. Leo warns when reading @file files, and changing @file trees in Leo v1.15 is not recommended.
This version corrects a problem with double-clicking Leo icons under Windows Me. You may now launch Leo by double-click the icon for any .leo file and you may open any .leo file by double-clicking on it.
This version of Leo corrects a problem with how whitespace was handled in @silent mode.
Barring serious bugs, version 1.12 is likely to be the last Borland/Windows version of Leo for a while. The wxWindows versions of Leo are usable enough to justify putting all further work into them.

There are several important improvements in version 1.12:

1. Added the Sort command to the Outline menu. This command sorts all the children of a node into alphabetical order.

2. A major improvement to Tangle. In @silent mode, Tangle now outputs derived files with "verbatim" whitespace, that is, without any extra blanks, tabs and newlines. This makes Tangle much more useful for Python, and in other applications. For example, it is now possible to do the following in Python:

if <<condition1>> && <<condition2>>: print <<list of vars>>

In the derived file, expansions of the sections will be placed on the lines in which the reference appears. This works in Python as long as the expansions themselves appear on a single logical line.

This "verbatim" expansion only happens in @silent mode, so these kinds of derived files can not be Untangled.

3. Tangle now reports errors more fully and less redundantly. Errors involving missing or erroneous path names (specified in @root directives, @path directives or the Preferences Panel) are more specific. These errors are reported only once per Tangle command, and these errors terminate the Tangle command immediately. This is wise; we don't want to write files to the wrong directory! Finally, Tangle no longer prints the actual text of a section when giving the "possibly duplicate section definition" error. This should be helpful: no longer are huge error messages generated. It should be easy enough to find the relevant section definitions using Leo's find command.
This version corrects a problem that can cause the Import CWEB Files command to hang in an endless loop.

This file also contains improved documentation (see the children of this node) in two sections:

1. Added an explanation of the following error message given by the Tangle command: "Multiple parts not allowed for << section name >>"

2. Added a warning about using Untangle to update cloned nodes. (See the last paragraph of the node.)
Sections can be defined in several parts in two ways:

1. Using << section name >>= in several places with the same section name.

2. Using several @code directives within the _same_ body text.

As a precaution against mistakenly defining a section in more than one place, it is invalid to use @code in different nodes to define multiple parts for the same section. In particular, this error may arise when using cloned nodes.

This error may always be eliminated by using << section name >>= instead of @code.

The Untangle, Untangle All and Untangle Marked commands are the reverse of the corresponding Tangle commands. They update an outline based on changes made to one or more derived files. These are exceptionally useful commands.

For example, suppose you create a new part of the outline and Tangle it for the first time. When you compile the resulting derived files for the first time, you are likely to get many syntax errors. You could fix those errors in the outline and Tangle the outline again, but there is a much easier way: you fix the errors in the derived files using the compiler's editor, then run the untangle command on the part of the outline that created the derived file. The Untangle command updates the selected outline to match the changes in the derived files. It's as simple as that. By the way, the Untangle command marks all the nodes in the outline that it updates, and you can examine all such nodes with the Go To Next Marked command in the Outline menu.

You cannot use Untangle to update doc parts, or leading comments in code parts or "trivial" whitespace in code parts. This is a limitation of the Untangle command that cannot be fixed; Untangle has no way of knowing whether leading comments came from doc parts or are just leading comments.

Untangle never changes the structure of an outline; it never inserts, deletes or moves nodes. Don't attempt to change the structure of an outline by modifying derived files; it won't work. Also, never delete, move or alter the sentinel lines in derived files written by the Tangle command. Such lines start with /// followed by a section name.

If you change the section name in a sentinel line Untangle will not update the code in the outline (with the old name) that generated the renamed section. Untangle now warns about sections that appear in a derived file but not in the outline. Untangle has no trouble with changed section references in derived files; it is only changed sentinel lines that cause problems.

Cloned nodes that generate code in several files may cause problems for Untangle if not all the code is changed the same way in derived files. If Untangle is run separately on these derived files, Untangle will update all cloned nodes each time it is run, so only the code in the last Untangle run will take effect. Therefore, the only reliable way to update text in cloned nodes is to make the change in the .leo file rather than the derived files.
Version 1.10 incorporates the following changes:

1. The Untangle command no longer updates @doc chunks under any circumstances. This makes Untangle safe to use when the "Tangle outputs doc chunks" preferences is not selected. In previous versions the Untangle command would delete all @doc chunks in the outline when a derived file had been Tangled without "Tangle outputs doc chunks".

The new version of Untangle is experimental; only one line of code has been added. I welcome any comments about this change. This change also fixes (or rather bypasses) a crasher reported in Untangle.

2. The shortcut for the Execute Script command has been changed from Alt-E to Shift-Alt-E to avoid conflicting with the shortcut that brings up the edit menu.

Version 1.9 contains several improvements that make Leo more Python-friendly:

1. The Execute Script command (Alt-E) in the Edit menu allows you to execute Python scripts directly from body text.

2. Several small changes make the Python window significantly easier to use.

Also, considerable reference (study-only) code has been removed from leo(CB).leo.
It is now possible to execute any Python script directly from any body text. Just select the script in the body text (including a trailing newline) and use the Execute Script (Alt-E) command from the edit menu. The script is copied to the Python window and executed as usual.
Several small changes make the Python window significantly easier to use:

1. The paste command now works in the Python window whenever the Python window is visible. This fixes a bug that required a mouse click in either pane before paste would work.

2. It is now possible to execute scripts multi-line scripts pasted into the input pane. Other changes make it easier (or possible) to terminate continued code.
Fixed a crasher that makes it impossible to tangle outlines containing doc parts in languages like HTML that have no single-line comment delimiter.
Leo v 1.7 accepts a slightly more general file format so that it can read XML generated from external XML tools. Leo v 1.7 can read all previous .leo files. Leo's Save commands remain unchanged.

Leo still only understands Leo files. However, Leo now understands a slightly generalized form of Leo files. Specifically:

1. Attributes may now appear in any order. (Leo writes attributes in a fixed order, but some XSLT tools rewrite Leo files with attributes in a different order.)

2. Leo will now accept <tag/> instead of <tag>stuff</tag> when "stuff" is empty.
This version corrects a small bug: the wrong text is selected when the syntax colorer changes. This is a cosmetic bug only; the selected text will not be replaced when further typing occurs.

Unless major bugs are found, this will be the last version of Leo for several months. I want to freeze the code so that I can work on the wxWindows version.
This version is a major milestone in Leo's code. Improvements were made throughout Leo's code in an attempt to correct an extremely hard-to-find bug that can cause data in body panes to be lost. Only time will tell if the bug has, in fact, finally been eradicated.

This version also corrects a minor bug; icons in a newly cloned tree were not always drawn properly. Also, the currently selected headline no longer changes if you click on the + or - icons that expand and contract headlines. This new behavior is a result of simplifying the event handlers; I won't consider changing this behavior until it is clear that the data loss bug has finally been fixed.

Use this version of Leo with caution until the new code has been thoroughly tested. New asserts will guard against silent data loss and other unexpected events. I don't expect these asserts to fail, but then one never does...

The following lists the changes that were made the code. You don't need to read the following details; they are provided only for completeness.

1. Greatly simplified the event handlers. This should have been done long ago.

The event handlers now handle _all_ aspects of switching tree nodes and updating data structures when the body pane changes. In particular, this guarantees that tnodes are "synched" when the body pane changes. This is a major simplification and a major improvement: it should no longer be possible to lose data once it appears in the body pane.

The reverse is not true: the code must still take care that the body pane is updated if tnodes are changed "by hand." (This is typically done when new nodes are being created. Such nodes are never the current node and so are not shown in the body pane.) I re-checked all code to make sure this constraint is satisfied.

Removed synchVnodes and updateBodyPane methods. These have always been the most error-prone methods in Leo. Removing them was made possible because the global constraint that tnodes are up-to-date is now guaranteed by the event handlers.

Created a new updateSyntaxColorer routine, which is called when moving a node or changing its text might affect syntax coloring. This routine can never affect tnodes.

2. Removed reference counting from tnodes. The new code deletes tnodes only during the read process. After that, tnodes are never deleted. This could be called a memory leak, but it ensures that tnodes can never be deleted out from under a vnode. I don't this was ever the problem with losing data, and the new code makes sure that it can't be.

3. Improved how icons are redrawn. Event handlers now set the dirty bit when text changes, which simplifies matters.

4. Added asserts to guard against silent data loss, unexpected calls to event handlers, and mismatches between vnodes and their commanders.

6. Added the mCommands data member to vnodes. This member ensures that the correct commander is returned by vnode::commands(). The old code got the commander by looking for the current window. Conceivably this could have caused the wrong commander to be used in some cases.
This version adds the following features:

1. Outline to noweb command. 2. Python scripts for finding and changing text in Leo outlines. 3. A Python method of the vnode class returning the commander corresponding to a vnode. 4. Documentation for Leo's import/export commands.

This may be the last version of Leo for a while; I am about to start work on a cross-platform wxWindows version of Leo. However, bug reports and requests for new features are always welcome.

Edward K. Ream September 19, 2000
The "Outline To Noweb" command converts the selected outline to a flat file containing noweb text. The name of the root section is taken from the @root directive. <<*>> is used if the @root directive is not followed by any file name.
Leo's Python scripting does not support Leo's Find and Replace commands directly because these commands are interactive. Calling these commands from a Python script would be clumsy.

Instead you can use the new find and change scripts to search and replace text. These scripts use Python's powerful re module.
The commands() method has been added to the Python vnode class.

c=v.commands()

This method returns the commander for the window containing v. This method can be used to avoid passing commanders as arguments to vnode methods. For example, given just v, we can call either of the following:

v.commands().synchNode() v.commands().Repaint()
This version corrects a problem with the Save As command; it worked only once! That is, after the Save As command was executed the window's title would change, but a later Save command would change the file specified before the Save As command was executed.
This version of Leo introduces Python scripting. This is a major new addition to Leo. See the new Chapter 7 of this documentation. Python support still has rough edges; the next release will be cleaner. Any suggestions for improvements to Leo's support for Python would be appreciated.

Alt-V now toggles the "View All Characters" option. This is especially useful when editing Python code.

Besides Python support, a number of minor bug fixes have been made:

Added syntax coloring for try and catch to the C parsers.

Control-K, I now indents blocks of text uniformly, regardless of the language being syntax colored, and regardless of @nocolor and @color directives. Similarly, Control-K, U now undents blocks of text. The old keys, control-[ and control-] only worked when the current language was C and syntax coloring was enabled.

Removed the menu items that had the enter key as a shortcut. The only way to create a new node now is with Control-I. Using the enter key for a shortcut is not a good idea: it causes problems throughout the code.

Fixed a bug in forgiving compare. The code that was calling compare_comments wasn't handling mismatched whitespace properly.

Got the Import command for Python working. I found a bug in Tangle: in @silent mode we must always output a newline in the place of the function name. Otherwise the Python indentation will be wrong.

The Untangle is now disabled when in @silent mode. Nothing good can happen in that case!

Fixed a minor problem with the @path directive: ff a _new_ file has not been saved we issue a warning, not an error if no path has been specified. We can't really test the @root command for a path because the @root command may be found much later. Fortunately, this warning will happen very rarely. It's important, however, to warn that the file may be written to the standard directory if no path is specified in the @root command. Otherwise, Leo's behavior would be quite confusing.
This version corrects an oversight in the Flatten Outline command. In order to recreate the outline structure it is not enough to know whether each node has children; you must also know how many children each node has.

The new version of the Flatten Outline command represents the outline the same way the Export MORE Text does: a child headline is indented one tab more than its parent headline.
The Tangle commands now examine all nodes, including cloned nodes that have previously been visited. Previously, such nodes were skipped during tangling. The new logic allows clones to represent code common to more than one unit. You can always insert an @ignore directive in an ancestor of a cloned node to prevent the node from being processed during tangling.
Version 0.19 fixes some bugs and eliminates some problematic features of Leo. Also, the role of CWEB mode has been documented more thoroughly.

The PC version of PC is feature complete. I will release version 1.0 after people have played with this version for a while.

As always, I'll be happy to fix bugs or add urgently needed features.
Fixed a bug that could result in Tangle outputting derived files in the wrong directory.

Tangle and Untangle now set the current directory using @path directive if it exists, otherwise the directory specified in the Preferences panel if it exists, otherwise the directory set by the Load or Save commands if it exists. (The Save As and Save To never alter this directory.) Finally, a check to see if the @root directive specifies a full path. If so, the current directory is not changed.

If none of these directories exists an error is raised so that no Tangling or Untangling is done. This error can only occur if Tangling or Untangling a file that has just been created by the New command and never saved with the Save command.
Added the @language html, @language perl and @language perlpod directives, and added HTML, Perl and Perl + POD items in the Preferences panel.

The HTML script is rudimentary at present. Suggestions for improvement are welcome.
The @cweb and @noweb commands are no longer functional. The new @language cweb directive replaces @cweb, and the other @language directives effectively replace @noweb. There is an new CWEB option in the Preference panel.
Leo now parses the @language directive as you type, so if you change the language specified by the @language directive you will see the effect immediately.

There is no longer a limit of 6 languages that can be syntax colored. Let me know if you would like Leo to support another language.
The Print command is now much more flexible and convenient: it will print any node and all its descendents, whether or not the node contains @unit, @root or @ignore directives.
Modified massage_block_comment so that it converts the first of a series of newlines to a blank and deletes the rest; Untangle no longer mangles modified @doc sections.

A more complete solution would be for Tangle to insert a flag for inserted newlines ( say backslash newline) so that Untangle could know which newlines to remove. I'd like to wait until all Tangle/Untangle code is converted to C++ before doing a more thorough job.
Leo no longer supports clone _windows_. Cloned outlines, however, will remain with Leo forever.

Clone windows were never very useful and were buggy. Eliminating clone windows has made it possible to handle events more smoothly and powerfully.
The Extended Noweb Syntax option in the Preferences panel is gone. This option has always been deprecated.
Version 0.18 changes the file format in an upward compatible way. That is, version 0.18 can read all previous versions of Leo files, but you must have version 0.18 of Leo to read files written by version 0.18.
Fixed a bug that caused @doc section not to be output properly for those languages like Fortran and Perl that do not have block comments.
The @noheader directive suppresses the header line normal written by Tangle at the start of each derived file. The @noheader directive is especially useful in Perl. Also added the corresponding "Tangle outputs header line" checkbox in the preferences panel. This checkbox sets the default which may be over-ridden by the @header or @noheader directives.
@language fortran specifies that comments start with C. @language fortran90 specifies that comments start with !.
@language perlpod specifies the Perl language with block comments being delimited by =pod and =cut
Version 0.17 fixes some bugs and puts some finishing touches on Leo.

The Borland C++ Builder version of Leo is now feature complete; there are no further items on the to-do list. Yes, I'll be happy to fix bugs or add urgently needed features.
Tangle and Untangle now treat <<*>>= as a synonym for @root filename if the _headline_ starts with text of the form @root filename. This convention allows full compatibility with noweb.
The @path, @tabwidth and @pagewidth directives are new.

These directives allow the user to override the defaults specified in the Prefs panel on a file-by-file basis. For the first time, every option in the Preferences panel may be overridden by a directive.
I fixed several severe bugs involving CWEB mode. It is now possible to tangle and untangle files when in CWEB mode, though CWEB mode has still not been tested thoroughly.
Reduced the default size of Leo windows so that they can be more easily resized on a small screen.
Version 0.16 is a significant new release of Leo. All users of the PC version of Leo should upgrade to version 0.16.

Significant changes have been made to Tangle and Untangle: these commands should now be able to handle code in almost any language. The new @language directive specifies the comment and string conventions used when Tangling and Untangling.

The @verbose, @terse and @silent directives are new: they control how Tangle outputs comments.

The Import Files command now handles Pascal, Python and Java files in addition to C and C++ files.

Besides the major changes a number of bug fixes and other minor features have been added.

Acknowledgements: Thanks to Zak Greant for numerous suggestions and bug fixes, and to Garold Johnson for improving this file.
Version 0.16 fixes all bugs mentioned on the SourceForge site as of April 27, 2000. These bugs involved problems with

@comment <empty> @comment 2 delims Not inheriting @comment properly non-delimited filenames in @root directives Crashes while Tangling Problems with Untangle with languages other than C.

Later sections discuss some of these in more detail.
Version 0.16 fixes a crasher related to changing windows.

This _may_ have been the source of crashes when cutting and pasting outlines, but don't count on it! Make sure you back up files _before_ cutting and pasting outlines!
The @language directive is new. The @language directive actually specifies the comment delimiters and string types used by the Tangle and Untangle.

The form of this directive is

@language x

where x is one of the following: c,c++,html,java,objective-c,pascal,perl,python and shell. Shell files have comments that start with #. Case is ignored in the language specifiers, but not in the @language itself. Thus, the following are equivalent:

@language html @language HTML @language hTmL

but the following is invalid:

@LANGUAGE html

The @comment directive is now officially deprecated: you should use the @language directive whenever possible. At present Untangle will not process an @root or @unit node if an @comment directive is in effect. Why? Because Untangle can't be sure of properly parsing a derived file if the language of the derived file isn't known. It might be possible to assume some defaults in this case, but that is not done at present and is not a high priority.
Scanning for @directives is now much faster. As a result, the @language and @comment directives will be recognized anywhere in an outline. This means

1) child nodes will "inherit" @language or @comment directives from ancestors unless over-ridden in the child node and

2) @language and @comment directives need not be placed in @unit or @root nodes. This is a much more intuitive and powerful way of handling these directives.
Key parts of the untangle logic have been generalized so that they can handle essentially any computer language. The following routines no longer have any C-specific code and have essentially been completely rewritten:

scan_derived_file (formerly scan_c_file) forgiving_compare (formerly c_compare)

Tangle now will properly output comments using the present comment delimiters as specified by either the @language directive or the @comment directive. As mentioned before, you should use @language instead of @comment wherever possible.
As a side effect of the Untangle command, Leo now removes trailing blank lines from the body text of all nodes being untangled. This finally crushes a long-standing bug. If the body text ends with one or more blank lines, the altered body text contains exactly one trailing newline.

Nodes that are altered in this way are not marked, nor is the file-changed mark set. This seems to be the most appropriate action to take: it would be confusing to prompt the user to save a changed file if the only changes involve trailing whitespace.
The @verbose, @terse and @silent directives determine how Tangle outputs comments. When @verbose is in effect Tangle outputs all comments. When @terse is in effect, Tangle outputs only those comments necessary for Untangle to work. When @silent is in effect Tangle no additional comments. Note: comments written by the user in code sections are always output: these directives control only:

1. The comments containing doc chunks and 2. Sentinel comments that delimit the beginning and end of code chunks.

@verbose is the default. If more than one of these directives appear in the same body text the "most verbose" of these options will be in effect.
At long last Leo has a useful notion of default directory. By default, the default directory is the directory containing the .leo file. This may be over-ridden using the "Tangle Default Directory" option in the Preferences panel.

The default directory is used whenever a file name in an @root directive contains no path component.
The Import Files command looks at the file extension to see how it should handle files.

Extension Language .c C .cpp C++ .java Java .pas Pascal .py Python

If the extension is not one of the above the Import files commands merely puts the entire file into the body text of a single node.

The Import Files command probably needs quite a bit of work.
Control-T now changes the pane that is currently active. If the outline pane was active, the body pane becomes active, and vice versa. This command makes it possible to switch between panes without using the mouse.
In previous versions of leo.leo I used slash instead of backslash as path delimiters. This should never have worked, but somehow it did. The present code uses the correct backslash in all @root directives.
Many improvements were made to the code. See the diary section of leo.leo if you are interested. In particular, I created args.h and args.c to define arguments. This cleans up the code quite a bit.
Changed Tangle and Untangle so that empty section names like << >> and << >>= are treated as ordinary characters. No warning is issued, which is debatable.
As mentioned in the main documentation, you must not change sentinel lines in derived files in any way. In particular, if you change the section name in a sentinel line Untangle will not update the code in the outline (with the old name) that generated the renamed section. Untangle now warns about sections that appear in a derived file but not in the outline. Untangle has no trouble with changed section references in derived files; it is only changed sentinel lines that cause problems.
Leo is being used to develop HTML files for web sites. In this context it is natural to define sections that are shared between files. Don't expect Untangle to update such sections if you make changes to them in derived files. The natural place to change sections shared by several roots is in the outline, not in the files derived from the outline.

If you do change code in a derived file that appears in several derived files, and try to use Untangle to update the code in the outline one of two things will happen:

1) You untangle an @root node. Untangle will never compare examine the common section of code for changes, as the common section appear under a @unit node, not the @root node. Untangle will complain about an orphan node.

2) You untangle an @unit node. Untangle will examine each @root node under the @unit node. Unless the change is made in the last root examined, Untangle will propagate the change when it first sees it, then go right ahead and undo the change when it sees the next expansion of the node in a derived file.

Untangle tests for inconsistent expansions of a section only within a single derived file. Such tests are not done between different derived files because we do not want disallow sections with the same names in different roots.
Known bugs and limitations in all versions of Leo.

1. Untangle has no way of updating a section(chunk) whose name has been changed in the derived file. Because of the @unit directive, there is not even a way to issue a meaningful warning.

2. Tangle treats @c like @code when in CWEB mode. I recommend changing @c to @< c @>= throughout your CWEB sources and adding a reference to @< c @> in all roots of CWEB files.

Known bugs and limitations of the PC version of Leo.

Syntax coloring options are not saved to the file.

The Log window has a limited capacity. This mainly affects debugging traces but could affect the output from the Tangle or Untangle commands if you tangled or untangled a large number of files.
@nocolor

This outline contains the latest documentation available for Leo.
@nocolor

The following was written by Joe Orr, a user of Leo. For more details, see http://www.jserv.com/jk_orr/xml/leo.htm

Leo is a powerful tool for organizing text into tree structures, and for just generally attacking a number of problems from a tree-based perspective.

<opinion> There has been a lot of hype about XML and all the uses to which it can be put. But, to my mind, the greatest benefit of XML is that it allows people to easily work with tree structures. All the benefits of XML really flow from that. Leo is the best application I've seen for conceiving and planning the trees that are then put into action via various XML technologies. </opinion>

Outlining Editors have existed for a while (e.g. PC-Outline, MORE) but Leo takes a major leap beyond previous tools for three reasons:

You can embed a simple macro language inside of a Leo document to instruct Leo to assemble pieces of the Leo document into another file. This facilitates, among other things, one type of Literate Programming.

Leo saves its files in XML format. This means that you can easily import and export Leo documents to any number of programs that can understand XML. for example, you can use an XSLT stylesheet to export a Leo file to HTML. And you can import any XML file into Leo using another XSLT stylesheet.

Leo is fully scriptable via Python.
@nocolor
Leo Users Manual June 15, 2002

Leo is a programmer's editor and a flexible browser for projects, programs, classes or data. Leo clarifies design, coding, debugging, testing and maintenance.

Leo is a literate programming tool, compatible with noweb and CWEB. Leo enhances any text-based programming language, from assembly language and C to Java and Python to XML and English.

Leo is an outlining editor. Outlines clarify the big picture while providing unlimited space for details.

Leo is also a data organizer. A single Leo outline can generate complex data spanning many different files. Leo has been used to manage web sites.

Leo is a project manager. Leo provides multiple views of a project within a single outline. Leo naturally represents tasks that remain up-to-date.

Leo is fully scriptable using Python and saves its files in XML format.

Leo is portable. Leo.py is 100% pure Python and will run on any platform supporting Python and Tk/tcl, including Windows, Linux and MacOS X. Leo.exe runs on any Windows platform.

Leo is Open Software, distributed under the Python License.

Leo was designed and built by:

Edward K. Ream email: edream@tds.net voice: (608) 231-0766
Leo creates a better way of programming by making clear the relationships between parts of programs. The larger the program, the more valuable Leo becomes. I would never willing program without Leo again.

Leo has hundreds of users. They use Leo to create programs in all kinds of languages, from assembly language, C and C++ to Java, Python and Tk. They use Leo to build and maintain web sites and to manage large amounts of other data. Leo's users have extended Leo in directions I never imagined. See the Acknowledgments below.

Inventing and building Leo, and completely re-visioning Leo to add @file trees has been the happiest and most creative part of my professional life. Leo grew out of my attempts to understand and use Donald Knuth's CWEB system of "literate programming". Chapter 6 discusses how Leo relates to traditional literate programming. Chapter 8 contains a history of Leo and a detailed account of how I invented Leo2.
This is what I wanted when I started this project.

1. A tool that I could use successfully. I had tried flat literate programming and failed to produce a program that I could understand. The design of my own literate programs were a mystery to me. With the insight gained with two years experience with literate outlines, I believe I could now probably create flat literate outlines with some success. But how would I do that? By mentally simulating a literate outline! So although it would be possible for me to use flat literate programming tools, it would never be easy or natural.

Using literate outlines instantly transformed how I program. No longer was I confused about what I had done or what remained to do. No longer did I need to remember section names. No longer did I have to search for where functions and variables were defined. March 5, 1996, the day I started using MORE as a prototype of a literate outliner, changed my programming life forever. Within an hour of writing code in MORE, I knew that literate outlines would work, and work spectacularly well.

2. A way to ignore formatting. Printed listings, no matter how "beautiful", simply do not convey the structure of complex programs as well as an on-screen outline. What matters is how a program is represented on screen. I have been using Leo since early 1996. Not once have I had any inclination to look at a printed listing in order to understand my program better.

3. An outliner as good as MORE. I accomplished this by borrowing a large part of MORE's screen design. Consider this the sincerest form of flattery.

I did make one real change to MORE's design. MORE shows body text in multiple separate windows within the main outline pane; Leo shows body text in a single body pane. Leo's approach simplifies switching between nodes: one simply selects a new headline and the body text appears in the body pane. With MORE, one has to explicitly open the body pane for each individual headline, which can be clumsy. However, MORE's approach does have some advantages. First, when opening body text in MORE, the subsidiary window becomes as large as necessary to show the entire body text, up to the limit of the size of the containing window. With Leo, the body pane stays fixed unless one resizes it. This is the reason Leo has several shortcuts for resizing the body pane.

MORE leaves lots of open windows around when doing a search or change command; MORE could be called a slob in this regard. However, MORE's way does have one benefit: one can see all the places where the search command found a match just be looking at all the open windows. With Leo this can not be done so directly; one must mark headlines to see where one has been. This is the main reason why Leo has a Find Next Marked Headline command. Without this command one must resort to something like expanding all headlines. Leo and MORE have superb outlining facilities; each provides true outline objects and a full range of outline operations. Leo attempts to fix the one clumsy feature of MORE, the need to explicitly open body text. On the whole, I believe that Leo is slightly easier to use than MORE, especially for programming.

4. A self-contained programming system. Leo's Tangle command provides basic support for literate programming. Leo's Untangle command is a novel addition to literate programming. The automatic tangling and untangling of @file trees is another major step forward. Finally, Leo's many import and export commands ensure that Leo is compatible with noweb and CWEB.

5. A fun tool. In Chapter 6 I list several highbrow reasons for liking literate programming. I know somebody gets what literate programming is all about when he or she says how much fun literate programming is. Clearly, Leo has succeeded admirably in this regard. I would never again willingly program without a literate outliner.
COPYRIGHT

All parts of Leo are distributed under the following copyright. This is intended to be the same as the Python license, namely that Leo is absolutely free, even for commercial use, including resale. There is no GNU-like "copyleft" restriction. This license is compatible with the GPL.

Copyright 1997-2002 by Edward K. Ream. All Rights Reserved.

Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Edward K. Ream or Leo not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission from Edward K. Ream.

DISCLAIMER OF WARRANTIES

EDWARD K. REAM (REAM) SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESSED OR IMPLIED, WITH RESPECT TO THIS COMPUTER SOFTWARE, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS RO A PARTICULAR PURPOSE. IN NO EVENT SHALL REAM BE LIABLE FOR ANY LOSS OF PROFIT OR ANY COMMERCIAL DAMAGE, INCLUDING BUT NOT LIMITED TO SPECIAL, INCIDENTAL, CONSEQUENTIAL OR OTHER DAMAGES.
Leo's main window contains an _outline pane_ and a _body pane_. An _outline_ represents an entire project and is stored in a single .leo file. An outline consists of _headlines_. Headlines may be expanded and contracted. Each headline has its own _body text_. The body pane shows the body text of the presently selected headline. A _node_ is a headline and its body text.

_Sections_ group arbitrary text into a meaningful unit. Sections increase the expressive power of any language, from C to Python to XML to English. Outlines may be reorganized freely because the meaning of a section does not usually depend on its position.

_Clones_ are parts of an outline that change in unison. Any change to a clone's headline, body text or its children is propagated to all other clones. Clones can represent tasks and clones can create multiple views of a project within a single outline.

_Derived files_ are files generated from parts of an outline. An outline may generate many derived files. Conversely, an outline may be updated using changes to derived files.

In short, sections simplify text, clones represent tasks, outlines organize projects and outlines generate derived files.
Leo's main window contains two panes, an outline pane on top and a body pane below. The outline pane contains a set of headlines. Headlines fold (expand and contract) like Windows Explorer when you click the + or - icons. Headlines group components (subheadlines) into larger units.

The bottom pane contains the body text associated with the selected headline. The combination of a headline and its associated body text is called a node. Body text can simply be plain text or text in the noweb or CWEB languages. A Leo program is a collection of all the body text of an outline organized by the headline.

Leo's outline are much more flexible and powerful than typical class browsers:

1. Leo's programming language understands outline structure. The programmer may name a part of the tree and refer to that tree by name in source code.

2. Leo's commands understand outline structure. Many of Leo's dozens of commands act on individual nodes and their descendents. For example, one may limit a Find or Change operation to a specified part of an outline.

3. Leo allows the user to reorganize outlines freely by adding, deleting and moving nodes.

4. Leo opens an outline as it was when the user last used it, restoring the selected node, the top visible node, and the expansion state of all nodes.
@nocolor

Text in Leo's body pane is simply plain text that may contain section references. For example, here is part of Leo's source code.

@color def removeSentinelsCommand (self,fileName): valid = true << Read file into s >> << Set delims from the header line >> if valid: s = self.removeSentinelLines(s,line_delim,start_delim,end_delim) newFileName = fileName + ".tmp" << Write s into newFileName >> else: es("Invalid @+leo sentinel in " + fileName)

@nocolor Section references have the form <<section name>> and may appear anywhere in code. Leo replaces a section reference by its definition when Leo creates a source (derived) file. Section references clarify code. In this example, the code that is represented by the three section references does not conceal the overall shape of the removeSentinelsCommand function. In particular, the role of the valid and s variable are clear. Section references are often more convenient than functions. For example, there is no need to return s from

<<Read file into s>>

or to pass s to

<<Write s into newFileName>>.

There are two ways to define sections. The first way is to use a section definition line,

<<section name>>=

(notice the equal sign) on a line by itself in body text, followed by the source code that defines the section. The second way is to create a node with <<section name>> in its headline and the source code that defines the section in its body text. Either way, the section definition may contain other section references.
Outlines can show relationships between sections. In the example above, a natural way of organizing the outline would as shown in the child of this node. The removeSentinelsCommand method is defined in one node which has three children, one for each section referenced in the definition of removeSentinelsCommand.

Furthermore, Leo uses outline structure to delimit derived files and to control the scope of section definitions. See Chapter 4 for details.
@color def removeSentinelsCommand (self,fileName):

path, self.fileName = os.path.split(fileName) # path/fileName trace(`self.fileName`) << Read file into s >> valid = true line_delim = start_delim = end_delim = None << set delims from the header line >> if valid == false: es("Invalid @+leo sentinel in " + fileName) else: trace("line:"+`line_delim`+","+ "start:"+`start_delim`+","+ "end:"+`end_delim`) s = removeSentinelLines(s,line_delim,start_delim,end_delim) newFileName = os.path.join(path,fileName+".tmp") << Write s into newFileName >>
try: file = open(fileName) s = file.read() file.close() except: es("Can not read " + fileName) return
@ This code is similar to atFile::scanHeader. @c

tag = "@+leo" # Skip any non @+leo lines. i = 0 while i < len(s) and not find_on_line(s,i,tag): i = skip_line(s,i) # We should be at the @+leo line. i = j = skip_ws(s,i) # The opening comment delim is the initial non-whitespace. while i < len(s) and not match(s,i,tag) and not is_ws(s[i]) and not is_nl(s,i): i += 1 if j < i: line_delim = s[j:i] else: valid = false # Make sure we have @+leo i = skip_ws(s,i) if match(s,i,tag): i += len(tag) else: valid = false # The closing comment delim is the trailing non-whitespace. i = j = skip_ws(s,i) while i < n and not is_ws(s[i]) and not is_nl(s,i): i += 1 if j < i: start_delim = line_delim end_delim = s[j:i] line_delim = None
try: file = open(newFileName,"w") file.write(s) file.close() except: es("Can not create " + newFileName)
Headlines (and the sections they contain) are the natural building block of tasks. To build tasks from headlines we need another feature of Leo's outlines, namely clones. A clone is a copy of a tree that changes whenever any part of the original tree changes. Clones are powerful. For example, adding a node to a cloned tree adds the same node to all cloned trees as well.

We can use an ordinary headline to represent any task as follows. We create an ordinary headline to represent the task and we label the headline with the name of the task. Let us call such a headline a task headline. Then, throughout the entire outline, we clone all headlines (sections) that pertain to the task and move the new clones as sub-headlines of the task headline.

In effect, the task headline becomes a separate view of the project. The task headline is a natural place to place notes about the task, for example, design documents, test data and logs, etc. Most importantly, the task view will change when any of its cloned headlines changes, and conversely, changes to clones under the task outline are propagated throughout the outline. This means that,

a) the task headline will remain up-to-date when the project changes, and b) we can make changes in the task view and rather than throughout an outline.

In short, task headlines focus our attention on only those parts of a project that relate directly to a task and makes it easy to change code scattered throughout a project.
Leo outlines are stored in a single .leo file. Leo can create many derived files from a single outline. You can create derived files explicitly using the Tangle command, or automatically when an outline file is saved. Conversely, Leo can update outlines based on changes to derived files. Again, you can update an outline explicitly using the Untangle command, or automatically when an outline file is opened. Outline files contain all information contained in derived files. This redundancy is useful for backing up data and for error recovery.

This introduction has only sketched Leo's capabilities. Chapter 3 shows how to create and reorganize Leo's outlines. This chapter covers clones, a powerful outline feature found in few other outliners. Chapter 4 tells you everything you need to write programs in Leo. Chapter 5 discusses all of Leo's commands. Chapter 7 discusses scripting Leo with Python.
@nocolor

This chapter tells how to install Leo.
@nocolor

This chapter tells how to use Leo's outline pane. It introduces terminology and discusses creating, moving, marking, cloning headlines.

This chapter also discusses the control keys that can be used in the body pane. This material is excerpted from documentation at http://tcl.activestate.com/man/tcl8.4/TkCmd/text.htm
A cloned node is a copy of a node that changes when the original changes. Changes to the children, grandchildren, etc. of a node are simultaneously made to the corresponding nodes contained in all cloned nodes. Clones are marked by a small arrow by their leader characters.

There is no real distinction between the "original" node and any of its clones. This makes it possible to update any of the clones and the original node will change as well. When the second-to-last cloned node is deleted the last cloned node becomes a regular node again.

Clones are useful for making alternate views of a program. For example, when I begin to fix a bug I clone all the sections of the code that relate to the bug, and place those cloned sections under a new headline whose name is the name of the bug I am fixing.

The children of this headline show some complex clones. To see how clones work, try moving the node marked f around with the nodes marked a fully expanded. Not all moves are allowed: you can't move a cloned node so it would become a child of another clone of itself.
This is a kind of bizarre outline that I use to test clone operations. Try moving the nodes a through g around and see what happens to the children of the various clones.
The Insert Node command inserts a new headline into the outline.

The Delete Node command deletes a headline and all its children.
The Cut Outline, Paste Outline, Copy Outline and Delete Outline commands work on nodes rather than text. For example, to delete a headline, select the headline and choose the Cut Outline or Delete Outline commands.

The Cut Outline and Copy Outline copy a text representation of the outline to the clipboard. This representation is the same as the file format with some information deleted. You may copy this text representation into a body pane (or into any other text editor) using the Paste command in the Edit menu.

Warning: In practice, it is almost always wiser to move clones rather than cutting or pasting them. Cutting and pasting outlines preserves clones, but the links between clones only exist within the part of the outline that was pasted. Therefore, if you are cutting and pasting an outline containing clones it is best to cut and paste the _entire_ outline. Alternatively, you can paste part of an outline, then delete all clones.
Body text in leo.py has the following features:

Leo auto indents when @nocolor is not in effect. That is, typing a newline automatically inserts the same leading whitespace present on the previous line. If the present language is Python (set either in the preferences or with an @language Python command), Leo inserts an additional tab if the previous line ends with a colon.

The default features of Leo's body text derive from the Tk Text widget, described at: http://www.tcl.tk/man/tcl8.3/TkCmd/text.htm Not all these features are found on all platforms.

The following features are mostly derived from the Tk Text widget. Some default behaviors have been changed because they conflict with other Leo features.

Clicking mouse button 1 positions the insertion cursor just before the character underneath the mouse cursor, sets the input focus to this widget, and clears any selection in the widget. Dragging with mouse button 1 strokes out a selection between the insertion cursor and the character under the mouse.

Double-clicking with mouse button 1 selects the word under the mouse and positions the insertion cursor at the beginning of the word.

The ends of the selection can be adjusted by dragging with mouse button 1 while the Shift key is down; this will adjust the end of the selection that was nearest to the mouse cursor when button 1 was pressed.

Clicking mouse button 1 with the Control key down will reposition the insertion cursor without affecting the selection.

If any normal printing characters are typed, they are inserted at the point of the insertion cursor.

If the mouse is dragged out of the body pane while button 1 is pressed, the entry will automatically scroll to make more text visible (if there is more text off-screen on the side where the mouse left the window).

The Left and Right (arrow) keys move the insertion cursor one character to the left or right; they also clear any selection in the text. If Left or Right is typed with the Shift key down, then the insertion cursor moves and the selection is extended to include the new character. Control-Left and Control-Right move the insertion cursor by words, and Control-Shift-Left and Control-Shift-Right move the insertion cursor by words and also extend the selection.

The Up and Down (arrow) keys move the insertion cursor one line up or down and clear any selection in the text. If Up or Right is typed with the Shift key down, then the insertion cursor moves and the selection is extended to include the new character. Control-Up and Control-Down move the insertion cursor by paragraphs (groups of lines separated by blank lines), and Control-Shift-Up and Control-Shift-Down move the insertion cursor by paragraphs and also extend the selection. Control-p and Control-n behave the same as Up and Down, respectively.

The Next(Page Down) and Prior(Page Up) keys move the insertion cursor forward or backwards by one screenful and clear any selection in the text. If the Shift key is held down while Next or Prior is typed, then the selection is extended to include the new character.

Line movement is by text lines terminated by hard returns (newlines), not by displayed lines; if a text line is long and wraps across more than one display line, then the Up and Down movement will skip the extra wrapped display lines.

Home moves the insertion cursor to the beginning of its line and clear any selection in the widget. Shift-Home moves the insertion cursor to the beginning of the line and also extends the selection to that point.

End moves the insertion cursor to the end of the line and clear any selection in the widget. Shift-End moves the cursor to the end of the line and extends the selection to that point.

Control-Home moves the insertion cursor to the beginning of the text and clear any selection in the widget. Control-Shift-Home moves the insertion cursor to the beginning of the text and also extends the selection to that point.

Control-End moves the insertion cursor to the end of the text and clear any selection in the widget. Control-Shift-End moves the cursor to the end of the text and extends the selection to that point.

The Select key and Control-Space set the selection anchor to the position of the insertion cursor. They don't affect the current selection. Shift-Select and Control-Shift-Space adjust the selection to the current position of the insertion cursor, selecting from the anchor to the insertion cursor if there was not any selection previously.

Control-/ selects the entire contents of the widget.

Control-\ clears any selection in the widget.

The F16 key (labeled Copy on many Sun workstations) or Control-c copies the selection in the widget to the clipboard, if there is a selection. This action is carried out by the command tk_textCopy.

The F20 key (labeled Cut on many Sun workstations) or Control-x copies the selection in the widget to the clipboard and deletes the selection. This action is carried out by the command tk_textCut. If there is no selection in the widget then these keys have no effect.

The F18 key (labeled Paste on many Sun workstations) or Control-v inserts the contents of the clipboard at the position of the insertion cursor. This action is carried out by the command tk_textPaste.

The Delete key deletes the selection, if there is one in the widget. If there is no selection, it deletes the character to the right of the insertion cursor.

Backspace deletes the selection, if there is one in the widget. If there is no selection, they delete the character to the left of the insertion cursor.
You can expand or contract a headline by clicking in the standard Windows Tree View icon to the left of the status icon.

Expanding a node shows its immediate children; contracting a node hides all its children. The Expand All Subheads command expands all of a nodes offspring (children, grandchildren, etc.)
Leo auto indents body text following colons when @language is in effect.

You may use Emacs-style auto-indentation instead. The child of this node provides full details.
You can mark headlines in several ways: with the various Outline:Mark commands, with the Find or Change commands, or with the Untangle command. The "Go To Next Marked" command will go to the next marked node, if any.

The Mark command will unmark the selected headline if it is already marked.
There are a number of ways to move and reorganize headlines.

The Move Up, Move Down, Move Left and Move Right commands move the currently selected outline. The Promote command makes all the children of a headline siblings of the headline. The Demote command makes all the siblings of a headline children of the headline.

You can cut and paste any part of a tree. If a headline contains selected text, the cut, copy, clear or paste operation affects only the selected text. Otherwise, the cut, copy, clear or paste operations acts on the node and all nodes contained by it. For example, you can move a headline by cutting it, selecting another location in the outline and pasting the headline in the new location.

Warning: In practice, it is almost always wiser to move clones rather than cutting or pasting them. Cutting and pasting outlines preserves clones, but the links between clones only exist within the part of the outline that was pasted. Therefore, if you are cutting and pasting an outline containing clones it is best to cut and paste the _entire_ outline. Alternatively, you can paste part of an outline, then delete all clones.
Double-clicking the icon box of a node whose headline has the form:

@url <any url>

executes the url in your default web browser. See the child of this node for full details.
You can change the relative sizes of the outline and body panes by dragging the splitter bar. The Equal Sized Panes command resizes the panes so that each fills half of the main window.
The outline pane supports one level of undo.

The body pane supports unlimited undo. However, you can not undo an operation affecting body text after selecting a new headline. You can undo or redo the last operation involving the outline itself. You can not undo operations affecting headline text.
@nocolor @ignore @language python
Important note 1: This chapter tells how to use the noweb markup language. See the node called About CWEB if you plan to use the CWEB markup language.

Important note 2: This chapter discusses only the @file directive.

-- Appendix 1 to this chapter tells how to use the new @rawfile directive. -- Appendix 2 to this chapter tells how to use the older @root directive.

This is the most important chapter because it describes everything you need to know to write programs with Leo. Unless otherwise mentioned, this chapter applies to all versions of Leo. Newcomers to Leo should read Introducing Leo before reading further.

I _strongly recommend_ that you have look at an example of a Leo outline like LeoPy.leo while you read this documentation. It is much easier to use Leo than to describe all the rules in detail. Looking at a real Leo outline will make many points clear that are difficult to describe succinctly in words.

Terminology: _Directives_ are commands that appear in body text. Directives start with an @ in the leftmost column followed by a directive name. We often refer to outline nodes by the directives they contain. For example, an @others node is a node containing an @others directive, an @ignore node is a node containing an @ignore directive, and so on. Exception: an @file node is a node whose headline starts with @file. We also speak of @root trees, trees whose is an @root node, @file trees, trees whose root is an @file node, and so on.

The essentials of writing programs in Leo are as follows:

1. A Leo program is composed the all the body text in an outline, or part of an outline.

2. Body text is a sequence of _sections_. There are two kinds of sections: doc sections and code sections.

3. Sections may contain _section references_ that refer to other sections by their nam.

4. Leo uses outline structure to control the scope of section definitions.

5. Leo creates source files called _derived files_ by expanding all section references in an @file tree.
@nocolor There are two kinds of sections: code sections and doc sections. Code sections start with the @c directive. Within @file trees, the entire body text of a node is a code section by default, so the @c directive can be omitted if there are no doc sections in the body pane. Doc sections start with @ followed by a space at the start of a line. Doc sections continue until the end of body text or until the next @c or @(space) directive.

The term "section" has two related meanings. Sections are syntactic units of text in the body pane. Leo writes the text of section to derived files, so another meaning of "section" is "the text that is written to the derived file." Which meaning is intended should be clear from context.

Code sections may contain section references of the form <<section name>>. That is, a section reference is any sequence of characters except newlines enclosed in << and >>. Leo ignores whitespace and case inside section names, so the following are equivalent: @color

<< Read file into s >> <<READ File into S>>

@nocolor Leo underlines section names whose definitions are not found in any descendent. Such sections are always invalid in @file trees, and may be valid in @root trees provided that the section is defined somewhere else.

Paired << and >> characters on the same line always denote a section name, even within comments and strings. That is, << and >> characters that do not delimit a section name must be placed on separate lines. If << and >> are not paired on a line, they are treated as literal << and >> characters.

Body text may contain zero or more sections in any order. A code section is named if the node's headline starts with <<section name>>. Otherwise, the code section is unnamed. Body text that contains no @ or @c directive is considered to be a single unnamed code section. @ and @c directives terminate any previous section. For example,

@color @ This method puts an open node sentinel for node v. @c def putOpenNodeSentinel(self,v): if v.isAtFileNode() and v != self.root: << issue an error message >> else: s = self.nodeSentinelText(v) self.putSentinel("@+node:" + s)

@nocolor An @c directive is optional at the start of body text, so: @color

@c def hasChildren(self): return self.firstChild() != None

@nocolor is equivalent to the following text without any markup: @color

def hasChildren(self): return self.firstChild() != None

@nocolor The @c directive is needed here: @color

@ Returns true if the receiver has child nodes. @c def hasChildren(self): return self.firstChild() != None

@nocolor However, we could replace the doc part with a comment, like this: @color

# Returns true if the receiver has child nodes. def hasChildren(self): return self.firstChild() != None

@nocolor The choice of using doc parts or comments is a matter of style. Doc parts are convenient for longer comments that would be tedious to format by hand.

Minor note 1: For historical reasons, the @code directive is a synonym for the @c directive and the @doc directive is a synonym for @ directive. Only the @c, @code, @ and @doc directives terminate sections.

Minor note 2: A line of the form:

@ %def identifiers

terminates the previous code section and indicates that the preceding code section defines the list of identifiers. This list contains identifiers separated by whitespace; any sequence of non-white characters may be an identifier. This construction is used in traditional noweb programming to generate indices.
Lets see how to organize typical outlines. The only rules we must follow are:

- All nodes containing body text must be referenced.

- Each named node must descend from the node that refers to it.

- The @ignore directive is not valid in @file trees.

A node that is not referenced is called an _orphan node_. If an @file tree contains an orphan node or @ignore node, Leo issues a warning and does not write the derived file when saving an outline. Leo saves all the information in the @file tree in the .leo file, so nothing is lost; Leo will recreate the @file tree from the .leo file when reading the outline the next time.

We can avoid having to refer to sections by name by using the @others directive. This can save a lot of work.

The @others directive refers to _all unnamed_ sections. An @file tree may contain more than one @others directive. @others directives that descend from other @other directives refer only to unnamed nodes that descend from them. The @others directive that occurs highest in the @file tree refers to all other unnamed nodes.

There are two minor restrictions on the @others directive:

- No single node may contain more than one @others directive.

- No named node may "intervene" between an unnamed node containing body text and an @others node. This restriction will be discussed in detail below. In practice this is never a problem.

When saving an outline, Leo creates derived files from all changed @file trees by expanding references to named and unnamed sections:

- Section references place the body text of a single named section in the derived file.

- @others directives place the body text of all unnamed nodes in the derived file.

- The @others directive places unnamed nodes in outline order, the order in which nodes appear in an outline when all nodes are expanded.

When reading an outline, Leo recreates @file trees from derived files. In particular, you may change derived files outside of Leo, and Leo will update the outline accordingly. You may change derived files in any way provided that you don't change sentinel lines. Sentinel lines are comment lines whose first character (after the comment delimiter) is @. Leo represents outline structure in sentinel lines, so changing sentinel lines will corrupt the outline structure. (The appendix lists the format and meaning of all sentinel lines.)

Some examples should make all this much clearer. See the children of this node.

The @ignore directive is not valid in @file trees because derived files contain primary data. The .leo file also contains a copy of the information in the @file tree, but this information is used for backup and error recovery. As a result, everything in an @file trees must correspond to information the derived file and the @ignore directive may not appear in @file trees.
@file f.c shows how to create a C file.

@file classX.py shows how to create a single Python class.

@file mainClass.py shows how to define two classes in a single Python file.

The @file bad tree is invalid. No named node may "intervene" between an unnamed node containing body text and an @others node.
@color @ This @file node generates the file f.c.

This body text ensures that the expansions of << constants >> and << declarations >> precede the expansions of all unnamed sections. It is good style to name those sections that must appear in a particular place in the derived file. All unnamed sections are placed in the derived file where the @others directive appears. Unnamed sections appear in the derived file the order in which they appear in the outline.

The children of the @file node contain the definitions of all sections, both named and unnamed. Unnamed sections usually contain functions or method. It is bad style for the meaning of a derived file to depend on the order of unnamed sections in the outline. @c

<< constants >> << declarations >> @others
the constants
the declarations
text of function 1
text of function 2
@color @ This @file node generates the file classX.py.

Indentation is significant in languages like Python. When Leo expands a section reference (or an @others directive), Leo adds the leading whitespace of the line containing the section reference to all lines of the reference's expansion. The text of body text need not be indented beyond its natural indentation.

Organizing nodes (nodes with no body text) do not affect the derived file in any way. In particular, such nodes never change indentation. Organizing nodes are often useful. @c

<< imports for classX >> class classX: @others
@ This is an organizing node. Using this node does not change the indentation in the derived file.
@ This is an organizing node. Using this node does not change the indentation in the derived file.
@color @ This @file node generates the file mainClass.py.

When two classes appear in the same file, a single @others directive does not suffice. However, no node may contain more than one @others directive. To get around this limitation we can organize the outline as shown.

The @others directive refers only to unnamed nodes in the descendents of the @others node, so the two @others directives refer to disjoint sets of unnamed sections. @c

<<mainClass imports>> <<class mainClass>> <<class helperClass>>
class mainClass: @others
class helperClass: @others
@color @language python @ The following outline is invalid. Node A is an unnamed node containing body text, and the <<section one>> node intervenes between Node A and the @others node. @c

class classX: @others
def bad(): << section 1 >>
@ This node is invalid because it intervenes between Node A and the @others node above.
def spam(): pass
The following sections discuss directives that have not been discussed previously. Unless otherwise noted, these directives are valid anywhere in an outline.
Syntax coloring is on by default in all body text. Leo formats comments and documentation parts in red, directives and C keywords in blue, strings and character constants in gray and all other text in code parts in black.

The @nocolor directive disables syntax coloring for the body text in which it appears. No syntax coloring is done until an @color directive re-enables syntax coloring.

If a node contains neither the @color nor the @nocolor directive it may inherit the syntax coloring attribute from an ancestor. The nearest ancestor that contains exactly one of the @color or @nocolor directives will control the syntax coloring. Ambiguous nodes, nodes containing both the @color and @nocolor directives, never affect the coloring of their offspring.

Note: the @color or @nocolor directives do not affect the Tangle commands in any way. In particular, the Tangle commands will recognize section definitions as usual even after an @nocolor directive is seen.
Note: the @comment directive is now officially deprecated: you should use the @language directive whenever possible.

Untangle will not process an @root or @unit node if an @comment directive is in effect because Untangle can't be sure of properly parsing a derived file if the language of the derived file isn't known. It might be possible to assume some defaults in this case, but that is not done at present and is not a high priority.

By default, the Tangle Commands produces C-language comments. Single-line comments generated during tangling start with ///, while documentation parts are surrounded by /* and */. The @comment directive allows you to use Tangle to produce shell and make files, as well as source code for other programming languages.

The @comment directive may be followed by zero to three delimiters, separated by whitespace. This directive sets the single-line comment delimiter and the opening and closing block comment delimiters as follows:

@comment (no delim) restores the defaults to ///, /* and */ @comment /// (one delim) sets the single-line comment and clears the other delimiters. @comment /* */ (two delims) sets the opening and closing block comment delimiters and clears the single-line comment. @comment /// /* */ (three delims) sets all three delimiters.

If only one delimiter is given, Leo does not write any documentation parts while tangling. If two delimiters are given, block-style comments are used instead of single-line comments. For example, the @comment { } directive could be used to Tangle Pascal files.

The @comment directive is only recognized in @root, @unit or @file nodes, and the @comment directive must precede the first section name or @code directive. An @comment directive in the body text of an @unit directive specifies the current global defaults. An @comment directive in the body text of an @root directive affects comments generated for one root only. Comments in all other roots are governed by the global defaults.

New in leo.py 3.0: Leo will convert underscores in the @comment directives to significant spaces. For example,

@comment REM_

causes the comment delimiter to be "REM " (Note the trailing space).
@nocolor

The @delims directive changes the comment strings used to mark sentinel lines. This directive is often used to place Javascript text inside XML or HTML files.

The @delims directive contains one or two delimiters, separated by whitespace. If only one delim is present it delimits single-line comments. If two delims are present they delimit block comments. The @delims directive can not be used to change the comment strings at the start of the derived file, that is, the comment strings for the @+leo sentinel and the initial @+body and @+node sentinels. The @delims directive inserts @@delims sentinels into the derived file. The new delimiter strings continue in effect until the next @@delims sentinel in the derived file or the end of the derived file.

Note: Leo can not revert to previous delimiters automatically; you must change back to previous delimiters using another @delims directive. For example:

@color

@delims /* */? Javascript stuff? @delims <-- -->? HTML stuff

@nocolor Adding, deleting or changing @@delims sentinels will destroy Leo's ability to read the derived file. Mistakes using the @delims directive have no effect on Leo, though such mistakes will thoroughly mess up a derived file as far as compilers, HTML renderers, etc. are concerned.
You may use the @encoding directive to specify the encoding used in a derived file. You can't mix encodings in a single derived file. For example,

@encoding iso-8859-1

If the encoding used in a derived file is not "utf-8" it is represented in the @+leo sentinel line, like this:

#@+leo-encoding=iso-8859-1.

The "utf-8" encoding is used by default. The "utf-8" encoding is a "lossless" encoding (it can represent all unicode code points), so encoding and decoding to and from utf-8 plain strings will never cause a problem. When reading or writing a character not in a "lossy" encoding (such as iso-8859-1), Leo converts such characters to '?' and issues a warning.
The @first directive allows you to place lines at the very start of files derived from @file nodes. For example, the body text of @file spam.py might be:

@first #! /usr/bin/env python

The body text of @file foo.perl might be:

@first #/usr/bin/perl

@first directives are recognized only at the start of the body text of @file nodes. No text may precede @first directives. More than one @first directive may exist, like this:

@first #! /usr/bin/env python @first # more comments.

Similarly, @last directives are recognized only at the end of body text of @file nodes. No text may follow @last directives. More than one @last directive may exist. For example, here is how a PHP file might be set up:

@first <?php ... @last ?>
@nocolor The @language directive specifies the comment delimiters and string types used by the Tangle and Untangle commands. This directive over-rides the default specified in the Preferences panel. The form of this directive is: @color

@language x

@nocolor where x is one of the following: c, c++, html, java, objective-c, pascal, perl, perlpod, plain, python and shell. Shell files have comments that start with #. Case is ignored in the language specifiers, but not in the @language itself. Thus, the following are equivalent: @color

@language html @language HTML @language hTmL

but the following is invalid:

@LANGUAGE html
@nocolor

The @path, @pagewidth and @tabwidth directives allow preferences to be set on a file-by-file basis: they override the corresponding defaults in the Preferences panel.

The form of the @path directive is @path filename, where filename is taken to be everything following @path to the end of the line.

If the filename in @file pathname or @root pathname is an absolute filename the location of the derived file is specified only by the filename. Otherwise, if the filename is a relative filename, the location of the derived file is relative to:

1. the directory specified the applicable @path directive, or 2. the "Default Tangle Directory" in the Preferences panel if no @path directive is in effect, or 3. the directory in which the .leo resides if the .leo file has ever been saved.

An error occurs if no absolute path can be computed according to these rules, or if the filename or directory does not exist.

The form of the @pagewidth directive is @pagewidth n, where n is a positive integer that indicates the width of tangled pages in columns. This setting only affects how Tangle outputs block comments.

The form of the @tabwidth directive is @tabwidth n, where n is a positive integer that indicates the width of tabs in spaces. This is used by Tangle to output leading whitespace.
@file trees are much easier to use than @root trees:

1. @file trees require less markup than @root trees; all sections in @root trees must have names, but @file trees usually contain many unnamed sections. Within @file trees, the @others directive acts like a reference to all unnamed sections.

2. The user must explicitly tangle and untangle @root trees using the Tangle and Untangle commands. Leo tangles and untangles @file trees automatically, tangling all changed @file trees when writing an outline and untangling all @file trees when reading an outline.

However, @root trees are more flexible than @file trees:

1. Within @file trees, sections must be defined in descendents of the node that contains the section reference.

2. Within @root trees a section may be defined anywhere within the @root tree, regardless of where the section is referenced. Moreover, the @unit directive expands the scope of section definitions in @root trees so that a section may be referenced in several @root trees. (See "Tangling @root trees with the Tangle commands for a complete description of the @root directives.)

3. The meaning of section definitions in @root trees are independent of their position within the tree.

@root trees differ from @file trees as follows:

- Only @root trees may contain section definition lines (see below.)

- Only @file trees may contain @others directives.

- @c directives require section names in the headline of @root trees.

- @c directives are optional at the start of body text in @file trees only. Body text without markup is ignored in @root trees; it creates unnamed code sections in @file trees.
@nocolor Just as with @file trees, @root trees may contain kinds of sections: code sections and doc sections. Code sections start with section definition lines (see below) or the @c directive. Doc sections start with @ followed by a space at the start of a line. Doc sections continue until the end of body text or until the next @c or @(space) directive.

The term "section" has two related meanings. Sections are syntactic units of text in the body pane. Leo writes the text of section to derived files, so another meaning of "section" is "the text that is written to the derived file." Which meaning is intended should be clear from context.

Body text in @root trees contain zero or more sections in any order. The @c directive starts a named code section if the node's headline starts with <<section name>>. Otherwise, the @c directive is invalid.

Section definition lines of the form:

<< section name>>=

(note the equal sign) also start named code sections. Named code sections in @root trees may be defined in several places. The definition of a named code section is the concatenation of all code sections with the same name. Body text that defines no code section is ignored.

As in @file trees, paired << and >> characters on the same line always denote a section name, even within comments are strings. That is, << and >> characters that do not delimit a section name must be placed on separate lines. If << and >> are not paired on a line, they are treated as literal << and >> characters.

At least one non-blank line must follow the section definition line. That is, empty sections are not allowed.

Here is a typical example of body text within an @root tree: @color

@ This method puts an open node sentinel for node v. <<atFile methods>>= def putOpenNodeSentinel(self,v): if v.isAtFileNode() and v != self.root: << issue an error message >> else: s = self.nodeSentinelText(v) self.putSentinel("@+node:" + s)

@nocolor Provided that the node's headline starts with <<atFile methods>>, the example above is equivalent to: @color

@ This method puts an open node sentinel for node v. @c def putOpenNodeSentinel(self,v): if v.isAtFileNode() and v != self.root: << issue an error message >> else: s = self.nodeSentinelText(v) self.putSentinel("@+node:" + s)

@nocolor We may not eliminate @c directives in @root trees. If we convert the doc part to a comment we are left with: @color

@c # This method puts an open node sentinel for node v. def putOpenNodeSentinel(self,v): if v.isAtFileNode() and v != self.root: << issue an error message >> else: s = self.nodeSentinelText(v) self.putSentinel("@+node:" + s)

@nocolor The following escape convention applies only in @root trees. Within a code parts @@ in the first column (and only in the first column) stands for a single @ sign.
@nocolor Each @root tree represents a single derived file. Tangling is the process of creating derived files from @file or @root trees. Leo tangles @file trees automatically whenever an outline is saved. The user must tangle @file trees explicitly using one of the Tangle commands.

Leo creates derived files by expanding all section references in an @root node. Leo expands a section reference by substituting the code section itself for the section reference. This is a recursive process: the substituted code section may contain other code references which are themselves expanded, and so on.

It is unusual and bad style for the meaning of an @root tree to depend on the position of nodes within it. The meaning of a section definition in an @root tree usually does not depend on is position, and we may place sections wherever we want. The outline provides a natural way of organizing an sections as follows:

Place the definition of a section S in a child of the node containing the reference to S.

If a section is referenced in more than one node, I usually place its definition in a node containing all the nodes that refer to it. Using this rule of thumb creates an outline whose structure mirrors the intrinsic organization of a program.

The Tangle command creates derived files from @root node. The @root directive indicates which sections constitute an output file. The text following a @root directive forms the entire content of the file, that is, after section references are expanded. An outline can contain arbitrarily many @root directives: Leo's Tangle commands will create one output file for each. The process of creating derived files is called "tangling" because the code from the outline is rearrange to create the derived files.

For example, the following @root section shows a typical way of specifying a header file xx.h. @color

@root"xx.h" #ifndef xx_defined #define xx_defined << declarations of public constants of the xx class >> << declarations of public types of the xx class >> << declarations of public variables of the xx class >> << public prototypes of the xx class >> #endif

@nocolor The Tangle commands will create the file xx.h from this body text by expanding all the section references. Incidentally, the introductory documentation will be included in the header file: any text preceding the @root directive is treated just like the documentation part of an section definition.

As another example, the following shows a typical way of specifying the corresponding xx.c file: @color

@root"xx.c" << public variables of the xx class >> << private types of the xx class >> << private variables of the xx class >> << private function prototypes of the xx class >> << methods of the xx class >>

@nocolor There are three menu commands that tangle an outline: Tangle, Tangle All and Tangle Marked. These commands are identical except for how much of the outline is tangled. The Tangle command tangles only the selected portion of the outline, the Tangle All command tangles the entire outline, and the Tangle Marked command tangles only marked headlines.

The @root directive has three forms. All three forms mean exactly the same thing. @color

@root filename @root "filename" @root <filename>

@nocolor

If filename is an absolute filename the location of the derived file is specified only by the filename. Otherwise, if the @root node contains a relative filename, the location of the derived file is relative to:

1. the directory specified the applicable @path directive, or 2. the "Default Tangle Directory" in the Preferences panel if no @path directive is in effect, or 3. the directory in which the .leo resides if the .leo file has ever been saved.

An error occurs if no absolute path can be computed according to these rules, or if the filename or directory does not exist.

The Tangle commands format derived files so they are easy to read. The .c file in the Examples folder shows how derived files are formatted by the Tangle commands.

There is one complication that we haven't discussed yet: the scope of section definitions. The scope of a definition is the tree in which the definition is known.

By default, Tangle commands look for section definitions only in the suboutline of the @root node being tangled. That is, all sections are assumed to be defined either in the body text of the headline, say h, containing the @root directive, or in the body texts of the descendants of h.

The @unit directive explicitly indicates the scope of section definitions. When a Tangle command encounters the @unit directive it treats the suboutline containing the @unit command as the scope for all enclosed roots. This ensures that the group of roots in the subtree use the same section definitions.

For example, suppose we have a tree organized as follows: + @unit + @root A sections in A + @root B sections in B

The @unit directive insures that: 1. only sections defined in the unit can effect files A and B and 2. all sections definitions in A and B are compatible with each other.

Finally, Tangle commands ignore any tree containing an @ignore directive. This ensures that trees that contain cloned nodes or other subsidiary information do not cause the tangle commands to issue spurious error messages. It also ensures that a tree can never contribute a section definition to another part of the outline by mistake.
The Untangle, Untangle All and Untangle Marked commands are the reverse of the corresponding Tangle commands. They update one or more @root nodes based on changes made to the corresponding derived files.

For example, suppose you create a new part of the outline and Tangle it for the first time. When you compile derived files for the first you are likely to get many syntax errors. You could fix those errors in the outline and Tangle the outline again, but there is a much easier way: you fix the errors in the derived files using the compiler's editor, then run the untangle command on the part of the outline that created the derived file. The Untangle command updates the selected outline to match the changes in the derived files. It's as simple as that. By the way, the Untangle command marks all the nodes in the outline that it updates, and you can examine all such nodes with the Go To Next Marked command in the Outline menu.

You cannot use Untangle to update doc parts, or leading comments in code parts or "trivial" whitespace in code parts. This is a limitation of the Untangle command that cannot be fixed; Untangle has no way of knowing whether leading comments came from doc parts or are just leading comments.

Untangle never changes the structure of an outline; it never inserts, deletes or moves nodes. Don't attempt to change the structure of an outline by modifying derived files; it won't work. Also, never delete, move or alter the sentinel lines in derived files written by the Tangle command. Such lines start with /// followed by a section name.

If you change the section name in a sentinel line Untangle will not update the code in the outline (with the old name) that generated the renamed section. Untangle now warns about sections that appear in a derived file but not in the outline. Untangle has no trouble with changed section references in derived files; it is only changed sentinel lines that cause problems.

Cloned nodes that generate code in several files may cause problems for Untangle. If Untangle is run separately on these derived files, Untangle will update all cloned nodes each time it is run, so only the code in the last Untangle run will take effect. Therefore, the safe way to update text in cloned nodes is to make the change in the .leo file rather than the derived files.
@nocolor

This chapter is discusses all of Leo's menu commands. It is organized by menu, and by function within each menu.

A note about typography: in this chapter command names and menu names will be capitalized, just as they would be in a menu. This convention allows us to write, for example, the Save A Copy As command, rather than having to use distracting quotation marks, like the "Save A Copy As" command.
The New command creates a new Leo main window. The Open command opens an existing Leo file and shows it in a main window. The Close command closes the topmost Leo window, and gives you an opportunity to save your work if you haven't yet done so.

The Save, Save As and Save A Copy As commands save the topmost Leo window to a file. The Save As and Save A Copy As commands create a complete copy of a Leo file. The Save command updates the file without rewriting those parts that have not been changed. As a result, the Save command is significantly faster than the Save As and Save A Copy As commands.

The Revert command reloads a file, discarding any changes made to the file since it was last saved.

New in leo.py 2.5: The Recent Files... command brings up a submenu containing a list of recently opened files. Choosing an item in this submenu opens the selected file or brings it to the front. Leo adds files to this submenu even if leoConfig.txt is missing or read-only, but those additions will not be remembered unless leoConfig.txt is present and writable.

The following four file commands are located in the File:Read/Write submenu.

Read Outline Only: reads an outline using only the .leo file, not any files derived from @file nodes. This command is useful for reverting a project to a previously saved state.

Read @file Nodes: updates all @file nodes in an outline. This ensures that the state of an outline matches all files derived from @file nodes. Included for completeness. This happens automatically when a .leo file is first read, and it also happens automatically when Leo2 is reactivated.

Write Outline Only: saves an outline without writing any @file trees. Useful for inserting an @file node into an outline without modifying a derived file with the same name.

Write @file Nodes: forces an update of all @file trees. Surprisingly useful.
The Open With command allows you to communicate with external editor. See the child of this node for full details.
The Tangle, Tangle All and Tangle Marked commands create derived files from portions of an outline. These commands indent and format the derived files so that they are easy to read and so that it is clear what sections produced the code. The .c file in the Examples folder shows the code produced by the Tangle commands.

The three Tangle commands are identical except for how much of the outline is tangled. The Tangle command tangles only the selected portion of the outline, the Tangle All command tangles the entire outline, and the Tangle Marked command tangles only marked headlines.

Derived files are usually .h or .c files (C), .cpp files (C++), or .m files (Objective-C) files, and they can also be other files such as "make" or "help" files.

The Tangle commands create a derived file, call it F, from each @root node. This process is very much like macro expansion. The contents of F are simply the body text of the @root node, with each section reference replaced by its definition. Substitution continues until all references to sections are replaced with their definitions.

By default, Tangle commands look for section definitions only in the suboutline containing the @root directive being tangled. That is, all sections are assumed to be defined either in the body text of the headline, say h, containing the @root directive, or in the body texts of the descendants of h.

The @unit directive changes the default scope of the tangle command, while the @ignore directive causes the tangle commands to ignore a subtree. For more details, see the discussion of the @unit and @ignore commands in Chapter called "Using noweb, CWEB and directives".
When using the Tangle and Untangle commands there are several issues to be aware of concerning your IDE (Integrated Development Environment). This section applies particularly to the Borland IDE; similar remarks apply to other IDE's.

Assuming you have added a .cpp file to your project, you update the file with the Tangle command. When you switch to the IDE you will get a dialog asking whether you want to update the file. Say yes and rebuild or remake.

You won't get the dialog unless the file is already open in the IDE. There is a bug in the Borland IDE: the dialog doesn't come to the front, so it can be easy to miss.

Now suppose you compile the file and get 20 errors all over the .cpp file. You fix the errors _in the IDE_, save the file, switch to Leo, and run the Untangle command. All your changes will appear in the outline! Each changed node will be marked by the Untangle command.

If you like you can use the Go To Next Marked command to find each changed node.
Note: the following describe the import and export commands found in leo.py. This documentation notes differences with the Windows version of Leo.

The Import to @file command creates an @file node from a file. The Import to @root command creates an @root node from a file. These commands do not attempt to do perfect translations; they merely automate the bulk of the drudgery. The Import to @root command is called Import Files in the Borland/Windows version of Leo. The Import to @file command does not exist in the Borland/Windows version of Leo. The Import CWEB Files command creates an @file node from a CWEB file. This command does not exist in the Borland/Windows version of Leo.

The Import noweb Files command creates an @file node from a CWEB file. The Borland/Windows version of this command creates an @root node.

The Import Flattened Outline command (called Import MORE Text in the Borland/Windows version of Leo) converts plain text written in MORE format to an outline. This command brings up a dialog which will accept at most one file. If that file contains MORE-format text it creates an outline corresponding to that text.

MORE is a now- defunct Macintosh outliner program. MORE represents outlines as follows. Headlines are denoted by a leading + or - character, preceding by zero or more tabs that denote the level of the headline. Body text follows its headline, with no indentation. The original MORE format did not escape lines in the body text that started with + or -. Leo escapes such characters by preceding +, - or backslash with a backslash.
The Outline To CWEB command creates a CWEB file from the selected outline.

The Outline TO noweb command creates a noweb file from the selected outline.

The Flatten Outline command creates a text file in MORE format from the selected outline. See the previous section for a discussion of the MORE format.

The Remove Sentinels command removes all sentinel lines from a file derived from an @file node.
The Quit command causes Leo to exit. You may also exit Leo by closing the main window. In either event you will be prompted to save any file that has been altered but not saved.
For leo.py version 2.0 and above:

leo.py supports unlimited Undo and Redo. All outline operations, including the Change All command and all operations and commands that affect body text may be undone and redone. Only the "Read @file Nodes" command is undoable; it clears the undo state. This command raises a dialog to allow the user to cancel.

Think of actions that may be Undone or Redone as a string of beads. A "bead pointer" points to the present bead. Performing an operation creates a new bead after the present bead and removes all following beads. Undoing an operation moves the bead pointer backwards; redoing an operation moves the bead pointer forwards. The Undo command is disabled when the bead pointer moves in front of the first bead; the Redo command is disabled when the bead pointer points to the last bead.

For all Borland versions:

The body pane supports unlimited undo. However, you can not undo an operation affecting body text after selecting a new headline. You can undo or redo the last operation involving the outline itself. You can not undo operations affecting headline text.
Leo supports the standard editing commands that appear in the edit menu: Undo, Cut, Copy, Paste and Clear. These commands work with either headline (when a headlines text is being edited) or body text. Text may be cut, copied and pasted between headline text and body text.

The Select All command selects all text in the body pane.
The Shift Left and Shift Right commands shift selected lines in the body text left or right one tab position. The entire line is shifted if any characters of the line are selected.
The Extract, Extract Section and Extract Section Names commands create child nodes whose headline is the first line of the selected body text.

The Extract command creates a new node whose headline is the first line of selected body text and whose body is all following lines of body text. All selected text is deleted from the original body text. This command is enabled only if the first line contains a section name (enclosed in angle brackets) on a line by itself.

The Extract Section command creates a new node whose headline is the first line of selected text and whose body is @code followed by all the other lines of selected text. All selected text lines except the first line are deleted from the original body text. This command is enabled only if the first line contains a section name (enclosed in angle brackets), on a line by itself.

The Extract Section Names command creates one or more child nodes, one for each section name (enclosed in angle brackets) in the selected body text. The headline of each created node is the section name and the body text of each created node is just @code followed by a newline.
New in leo.py v2.5.

The Convert Tabs command converts leading tabs to blanks in a single node. The Convert Blanks command converts blanks to tabs in a single node. The Convert All Tabs converts leading tabs to blanks throughout the selected tree. The Convert All Blanks command converts leading blanks to tabs throughout the selected tree.

All these commands convert between tabs and blanks using the tab width shown in the Preferences panel.
The Execute Script command executes body text as a Python script. Leo execute the selected text, or the entire body text if no text is selected.

Scripts are executed in a "pristine" environment, that is, with __builtins__ as both the local and global environments.
The Edit menu contains four commands for searching and replacing text. The Find and Find Again commands search through the outline for text and the Change and Change, Then Find commands change selected text.

The following check boxes options appear in the search dialog and control the operations of the find and change commands.
When checked, the Find and Change commands proceed without stopping. Output is sent to the log window regarding the progress of the command.
When checked, the Find and Change commands ignore the case of alphabetic characters when determining matches.
When checked, the Change command marks all headlines whose headline or body text are changed by the command.
When checked, the Find and Change commands mark all headlines in which a match is found with the pattern.
When checked, the Find and Change commands treat several characters specially in the find pattern. The '*' character matches any sequence of zero or more characters. The '.' character matches any single character. The '^' character at the start of a pattern, or a '$' character at the end of a pattern matches a newline.

Examples: The pattern "^abc$" matches lines that only contain "abc". The pattern "^a" matches any line starting with "A". The pattern "a$" matches any line ending with "a". The pattern "^*$" matches any line at all.
When checked, the Find and Change commands search backward through the file.
When checked, the Find and Change commands search body text.
When checked, the Find and Change commands search headline text.
When checked, the Find and Change commands search only the currently selected headline and its offspring.
When checked, the find pattern must match an entire word. Words consist of an alphabetic character or underscore, followed by zero or more alphabetic characters, numbers or underscores.
When checked, the Find and Change commands continues at the top of the file when the command reaches the bottom of the file. For reverse searches, the find or change command continues at the bottom of the file when the command reaches the top of the file.
The Go To Line Number command selects the locations in your outlines corresponding to a line in a derived file. See the child of this node for full details.
New in leo.py 3.11: Insert Body Time/Date and Insert Headline Time/Date commands. These are in the Edit Body and Edit Headline menus respectively. These commands insert the formatted time & date into t body or headline text. You must be editing a headline to be able to insert the time/date into the headline. If you aren't Leo will say: "Edit headline to append date/time"

Leo allows you to specify your prefered format for time and date using settings in leoConfig.leo/.txt: body_time_format_string in << body pane options >> and headline_time_format_string in << outline pane options>>

These settings specify the format string passed to time.strftime(format). For a complete list of the format options see http://www.python.org/doc/current/lib/module-time.html

If the format specified by either of these two settings is erroneous the "%m/%d/%Y %H:%M:%S" format is used by default, resulting in a time/date format like 1/30/2003 8:31:55
The Reformat Paragraph command allows you to easily reformat body text. See the child of this node for full details.
The Match Brackets command is enabled if the cursor is next to one of the following characters in the body pane:

( ) [ ] { } < >

This command looks for the matching character, searching backwards through the body text if the cursor is next to ) ] } or > and searching forward through the text otherwise. If the cursor is between two brackets the search is made for the bracket matching the leftmost bracket.

If a match is found, the entire range of characters delimited by the brackets is highlighted and the cursor is placed just to the left of the matching characters. Thus, executing this command twice highlights the range of matched characters without changing the cursor.
New in leo.py 3.11: The Preferences Panel now contains a checkbox called "Replace tabs with spaces". It is no longer necessary to type a negative tab width to indicate that tabs are to be converted to spaces.

New in leo.py 3.0: The Set Colors command brings up a dialog specifying colors for syntax coloring. You can specify colors for each syntax coloring construct using a color wheel or by choosing one of the standard Tk color names. When a color becomes active Leo writes its value or color name to the log pane for your reference. This is useful when setting coloring options in leoConfig.txt.

New in leo.py 3.0: The Set Font command brings allows you to specify the fonts used in the body pane, the outline pane (in headlines) and in the log pane. A font becomes active when you double-click its name, or check or uncheck any checkbox. When a font becomes "active", the new font appears in the panes whose check boxes are selected. Font sizes must be greater than 0 and less than 100 for them to become in effect. When you choose OK or Cancel Leo writes the fonts presently active in each pane to the log pane for your reference. This is useful when setting font options in leoConfig.txt.

The Preferences command brings up the preferences dialog. The Page Width option determines where Tangle will break lines. The other two global options control whether the Tangle and Untangle commands will execute a batch file when they are finished. The Default Target Language determines the default syntax coloring and the comment delimiters used by default when Tangling.

New in leo.py 2.5, negative tab widths: Leo converts tabs to blanks as you type when a negative tab width is specified.
Leo understands the following PHP keywords:

<?php, ?>, __CLASS__, __FILE__, __FUNCTION__, __LINE__, and, as, break, case, cfunction, class, const, continue, declare, default, do, else, elseif, enddeclare, endfor, endforeach, endif, endswitch, endwhile, eval, extends, for, foreach, function, global, if, new, old_function, or, static, switch, unset(), use, var, while, xor,

Leo does not yet color the following keywords containing():

array(), die(), echo(), empty(), exit(), include(), include_once(), isset(), list(), print(), require(), require_once(), return()
Leo understands the following LaTex keywords: capitalization is significant. For example, \vert produces a single vertical line and \Vert produces a double vertical line.

\@, \(, \), \{, \},

\acute, \addcontentsline, \addtocontents, \addtocounter, \address, \addtolength, \addvspace, \AE, \ae, \aleph, \alph, \angle, \appendix, \approx,\arabic, \arccos, \arcsin, \arctan, \ast, \author,

\b, \backmatter, \backslash, \bar, \baselineskip, \baselinestretch, \begin, \beta, \bezier, \bf, \bfseries, \bibitem, \bigcap, \bigcup, \bigodot, \bigoplus, \bigotimes, \bigskip, \biguplus, \bigvee, \bigwedge,\bmod, \boldmath, \Box, \breve, \bullet,

\c, \cal, \caption, \cdot, \cdots, \centering, \chapter, \check, \chi, \circ, \circle, \cite, \cleardoublepage, \clearpage, \cline,\closing, \clubsuit, \coprod, \copywright, \cos, \cosh, \cot, \coth,csc,

\d, \dag, \dashbox, \date, \ddag, \ddot, \ddots, \decl, \deg, \Delta, \delta, \depthits, \det, \DH, \dh, \Diamond, \diamondsuit, \dim, \div, \DJ, \dj, \documentclass, \documentstyle, \dot, \dotfil, \downarrow,

\ell, \em, \emph, \end, \enlargethispage, \ensuremath, \enumi, \enuii, \enumiii, \enuiv, \epsilon, \equation, \equiv, \eta, \example, \exists, \exp,

\fbox, \figure, \flat, \flushbottom, \fnsymbol, \footnote, \footnotemark, \fotenotesize, \footnotetext, \forall, \frac, \frame, \framebox, \frenchspacing, \frontmatter,

\Gamma, \gamma, \gcd, \geq, \gg, \grave, \guillemotleft, \guillemotright,\guilsinglleft, \guilsinglright,

\H, \hat, \hbar, \heartsuit, \heightits, \hfill, \hline, \hom, \hrulefill,\hspace, \huge,\Huge,\hyphenation

\Im, \imath, \include, includeonly, indent, \index, \inf, \infty, \input, \int, \iota,\it, \item, \itshape,

\jmath, \Join,

\k, \kappa, \ker, \kill,

\label, \Lambda, \lambda, \langle, \large, \Large, \LARGE, \LaTeX, \LaTeXe, \ldots, \leadsto, \left, \Leftarrow, \leftarrow, \lefteqn, \leq, \lg, \lhd, \lim, \liminf, \limsup, \line, \linebreak, \linethickness, \linewidth,\listfiles, \ll, \ln, \location, \log, \Longleftarrow, \longleftarrow, \Longrightarrow,longrightarrow,

\mainmatter, \makebox, \makeglossary, \makeindex,\maketitle, \markboth, \markright, \mathbf, \mathcal, \mathit, \mathnormal, \mathop, \mathrm, \mathsf, \mathtt, \max, \mbox, \mdseries, \medskip, \mho, \min, \mp, \mpfootnote, \mu, \multicolumn, \multiput,

\nabla, \natural, \nearrow, \neq, \newcommand, \newcounter, \newenvironment, \newfont, \newlength,\newline, \newpage, \newsavebox, \newtheorem, \NG, \ng, \nocite, \noindent, \nolinbreak, \nopagebreak, \normalsize, \not, \nu, nwarrow,

\Omega, \omega, \onecolumn, \oint, \opening, \oval, \overbrace, \overline,

\P, \page, \pagebreak, \pagenumbering, \pageref, \pagestyle, \par, \parbox,\paragraph, \parindent, \parskip, \part, \partial, \per, \Phi, \phi,\Pi, \pi, \pm, \pmod, \pounds, \prime, \printindex, \prod, \propto, \protext, \providecomamnd, \Psi,\psi, \put,

\qbezier, \quoteblbase, \quotesinglbase,

\r, \raggedbottom, \raggedleft, \raggedright, \raisebox, \rangle, \Re, \ref, \renewcommand, \renewenvironment, \rhd, \rho, \right, \Rightarrow,\rightarrow, \rm, \rmfamily, \Roman, \roman, \rule,

\s, \samepage, \savebox, \sbox, \sc, \scriptsize, \scshape, \searrow, \sec, \section, \setcounter, \setlength, \settowidth, \settodepth, \settoheight, \settowidth, \sf, \sffamily, \sharp, \shortstack, \Sigma, \sigma, \signature, \sim, \simeq, \sin, \sinh, \sl, \SLiTeX, \slshape, \small, \smallskip, \spadesuit, \sqrt, \sqsubset, \sqsupset, \SS, \stackrel, \star, \subsection, \subset, \subsubsection, \sum, \sup, \supressfloats, \surd, \swarrow,

\t, \table, \tableofcontents, \tabularnewline, \tan, \tanh, \tau, \telephone,\TeX, \textbf, \textbullet, \textcircled, \textcompworkmark,\textemdash, \textendash, \textexclamdown, \textheight, \textquestiondown, \textquoteblleft, \textquoteblright, \textquoteleft, \textperiod, \textquotebl, \textquoteright, \textmd, \textit, \textrm, \textsc, \textsl, \textsf, \textsuperscript, \texttt, \textup, \textvisiblespace, \textwidth, \TH, \th, \thanks, \thebibligraphy, \Theta, theta, \tilde, \thinlines, \thispagestyle, \times, \tiny, \title,\today, \totalheightits, \triangle, \tt, \ttfamily, \twocoloumn, \typeout, \typein,

\u, \underbrace, \underline, \unitlength, \unlhd, \unrhd, \Uparrow, \uparrow,\updownarrow, \upshape, \Upsilon, \upsilon, \usebox, \usecounter, \usepackage,

\v, \value, \varepsilon, \varphi, \varpi, \varrho, \varsigma, \vartheta, \vdots, \vec, \vector, \verb, \Vert, \vert, \vfill, \vline, \vphantom, \vspace,

\widehat, \widetilde, \widthits, \wp,

\Xi, \xi,

\zeta
The Insert Headline command inserts a new headline after the presently selected headline, either as the next sibling or the first child of the presently selected headline if the presently selected headline has children and is expanded.
The Cut Outline, Paste Outline, Copy Outline and Delete Outline commands work on nodes rather than text. For example, to delete a headline, select the headline and choose the Cut Outline or Delete Outline commands.

The Cut Outline and Copy Outline copy a text representation of the outline to the clipboard. This representation is the same as the file format with some information deleted. You may copy this text representation into a body pane (or into any other text editor) using the Paste command in the Edit menu.

To Copy an outline from Borland Leo to leo.py:

1. Copy the tree in Borland Leo. 2. Paste the tree into empty body text of leo.py. 3. Select all the body text and choose Paste from the Edit Menu. 4. Move to the tree view, and choose Paste Node.

Yes, this is clumsy. I do not as yet know how to read the System clipboard using Tk, so I use the Tk.Text command to read it for me. Hopefully this unpleasantness will go away soon.

Warning: In practice, it is almost always wiser to move clones rather than cutting or pasting them. Cutting and pasting outlines preserves clones, but the links between clones only exist within the part of the outline that was pasted. Therefore, if you are cutting and pasting an outline containing clones it is best to cut and paste the _entire_ outline. Alternatively, you can paste part of an outline, then delete all clones.
The Expand command expands the currently selected node so that all its children are visible. The Expand All Subheads command expands the currently selected node so that all its offspring are visible. The Expand All command expands all the nodes of the entire tree.

New with leo.py version 1.0 and Borland Leo v2.6:

The Contract Parent contracts the parent node of the present node, making the parent the new present node.
The Move Up, Move Down, Move Left and Move Right commands move the currently selected node in the indicated direction. The Promote command makes all the children of a node siblings of the node. The Demote command makes all the siblings that follow a node children of the node.

The Sort Children command sorts all children of the present node in alphabetical order, based on the headlines of the children.

New in version 1.0 of leo.py and version 2.6 of Borland Leo.

The Sort Siblings command sorts all siblings of the present node in alphabetical order.
The Mark Headline command marks a headline with a red marker near the leader characters, while the Unmark Headline command removes such a mark. Only one of these commands appear in the Outline menu at any time: the Mark Headline command appears if the currently selected headline is unmarked. Otherwise, the Unmark Headline command appears. The Mark Subheads command marks all offspring of the currently selected node. The Unmark All command removes the marks from the entire tree.

The Mark Changed Items command marks all headlines whose headline or body text has been changed since the file was last saved. The Mark Changed Roots command marks all changed headlines whose body text contains the @root directive. This command is especially useful with the Tangle Marked command.
The Equal Sized Panes command adjusts the sizes of the code and body pane of the topmost window so that they are the same height.
The Cascade command cleans up the screen by cascading all Leo windows. The Minimize All command minimizes all Leo windows.
This command changes the pane that is currently active. If the outline pane was active, the body pane becomes active, and vice versa. This command makes it possible to switch between panes without using the mouse.
New in leo.py 3.0: The Toggle Split Direction command switches between vertical and horizontal orientations of the Leo window. In the vertical orientation, the body pane appears below the pane containing the outline and log panes. In the horizontal orientation, the body pane appears to the left the pane containing the outline and log panes.

leoConfig.txt specifies the default direction. If leoConfig.txt does not exist, or does not specify the direction, the "vertical" orientation is used, just as in previous versions.

By default, the ratio of pane outline pane to the body pane is 0.5 in the vertical orientation and 0.3 in the horizontal orientation. These two ratios may be changed using leoConfig.txt.
The About Leo command puts up a dialog box showing the version of Leo.
The Online Home Page command opens Leo's home page at http://personalpages.tds.net/~edream/front.html.

The Open Online Tutorial command opens Joe Orr's excellent ScreenBook tutorial at http://www.evisa.com/e/sbooks/leo/sbframetoc_ie.htm.

The Open Offline Tutorial command opens the file sbooks.chm if it exists in the folder containing leo.py. If sbooks.chm does not exist, you will be asked whether you want to download it from Leo's SourceForge web site. If you say yes, the page http://sourceforge.net/project/showfiles.php?group_id=3458 will open. You should then download sbooks.sbm to the folder containing leo.py.

The Open LeoDocs.leo command opens this file, leoDocs.leo.

The Open LeoConfig.leo command opens the file leoConfig.leo, the primary source file for the derived file leoConfig.txt. Use the Tangle All command to update leoConfig.txt from changes made to leoConfig.leo.

The Apply Settings command applies the settings in leoConfig.txt. Leo reads these settings when it begins execution, so the only time you would need to use this command is just after changing leoConfig.txt.
@nocolor

This chapter discusses Leo's relationship with traditional literate programming.
The following sections discuss the main benefits of traditional literate programming. It's worth noting that none of these benefits derives from being able to produce printed output.
Sections add a new dimension to the design and coding process. Choices about what sections do, what they are named, what order they appear in, are choices in a design space different from "normal" programming. This an abstract concept, to be sure. However, the following concrete discussions are really manifestation of working in this new design space.
The names of sections are constrained only by one's design skill, not by any rules of language. You say what you mean, and that becomes both the design and the code. You never have to simulate a concept because concepts become section names.
The visual weight of a web section is simply the length and complexity of the section's name, regardless of how complex the actual definition of the section is. The results of this separation are spectacular. No longer is one reluctant to do extensive error handling (or any other kind of minutia) for fear that it would obscure the essence of the program. Donald Knuth stresses this aspect of literate programming and I fully agree.
Sections can show and enforce relationships between apparently unrelated pieces of code. Comments, macros or functions are other ways to indicate such relationships, but often sections are ideal. Indeed, a natural progression is to create sections as a matter of course. I typically convert a section to a function only when it becomes apparent that a function's greater generality outweighs the inconvenience of having to declare and define the function.
A section name is complex when it implies unwholesome dependencies between the caller (user) of the section and the section itself. Such section names tend to be conspicuous, so that the programmer is lead to revise both the section name and its purpose. Many times my attention has been drawn to a poorly conceived section because I didn't like what its name implied. I have always been able to revise the code to improve the design, either by splitting a section into parts or be simplifying its relation to colleagues.
One of the most surprising thing about literate programming is how severely traditional programming tends to limit comments. In a conventional program the formatting of code must indicate structure, and comments obscure that formatting. Sections in literate programming provide a place for lengthy comments that do not clutter up the code at the place the section is referenced.
The section name often says it all. The reference to the section says everything that the user needs to know, and the section name at the point of definition also eliminates the need for many comments.
@nocolor

A typical root starts out with something like. @color

<< includes for class x >> << private data for class x >> << private prototypes for class x >> << functions of class x >>

@nocolor In the derived file there is a comment that looks like this: @color

/// << includes for class x >>

@nocolor It would be silly to write this comment by hand, though often programmers do just that in order to have a place holder for a mark in the source file. With literate programming the situation is different: the comment indicates that the code came from a particular section; that is, the comment servers a real purpose.
These last several paragraphs have discussed comments in detail because the net effect of "putting comments where they belong" is that comments don't clutter the code. Section references hide irrelevant detail, so larger-scale patterns within functions (or declarations) become more apparent. Often just recasting code into web format has created Ahas about my own code, with no special attention to recoding or redesign! Recasting a function as a web raises the real and apparent level of abstraction.
Formatting no longer has to indicate overall design; sections do that. I am less obsessive about formatting code; it simply doesn't matter much whether different sections are formatted consistently because the format of one section has no effect on the look of other sections. Also, I don't worry about most line breaks within documentation parts, or about adding comment delimiters.
Outlines add something brand new to traditional literate programming, namely an explicit mechanism for expressing structure at any level of detail, from largest overall view to smallest detail. The following sections elaborate on this theme.
By far the biggest drawback of conventional literate programming is that there are so many sections; it becomes difficult to understand the relationships, if any, between sections. Using an outline to express a literate programming instantly solves this problem. The programmer is always aware of how sections are related.
Outlines provide a convenient way of expressing the intended scope of commands. For example, the Tangle command operates only on the presently selected tree. The Extract Section command creates a new section as the last child of the present node.
Clones transform a tree into a directed graph without loops. This is a very powerful data structure. Nothing would be gained by cloning an arbitrary directed graph: in general, the clone would simply become the entire tree. Cloning a tree, however, is very effective.

A set of clones becomes essentially a different view of the outline. The set is itself part of the outline, so the outline itself contains an elegant mechanism for representing arbitrarily many views of the same data. It is because of the power of clones that I described MORE in the dedication as possibly the most elegant computer program ever written.

It is easy to create different (and useful) view of a literate outline. For example, I often make clones of all relevant sections of my outline while debugging or adding new features. The clones carry along relevant suboutlines, so essential context is retained, but gathering all the clones together automatically concentrates attention on the problem at hand.
The meaning of a well written literate outline is independent on the order in which sections appear, so outlines may be reorganized at will. In addition, headlines without body text do not affect derived files in any way; such headlines can be inserted freely at any time. In fact, such "nonfunctional" headlines often convey the most information about the structure and design of a large system. Decoupling structure from content in this way is precisely what is needed for flexibility: one can reorganize at will without worrying about changing the meaning of the code.
Hierarchy is often implicit in programming: for example, the grouping of functions into files, or the organization of a single file as a set of functions, etc. An outline directly expresses hierarchy. For example, Leo's tangle or Find and Change commands may be restricted to a particular suboutline. That's a direct expression of an hierarchical constraint or property: I only want to deal with this part of the outline. With outlines, I can do so effortlessly.

Traditional literate programming often simulates the hierarchy using "bridge" comments. Those comments are far weaker than explicit structure. Experience with the Print command clearly shows that a clear literate outline can easily become unintelligible when printed, no matter how "beautiful" the typeset printout is.

My experience shows that organizational crutches such as printed listings, tables of contents and indices are much less clear than the corresponding outline. You could say the entire outline is a table of contents!
There are many ways to express a program as a literate outline. Again, the choices made when doing so are choices in another design space. Such choices are important, and add clarity to the entire program, but can only be expressed when the hierarchy is expressed explicitly.
Tangling and untangling are the fundamental operations of literate programming. Any editor that supports literate programming should provide these operations directly. Moreover, Leo automatically untangles @file trees when reading .leo files and tangles @file trees when writing .leo files. Eliminating the need for explicit tangling and untangling is a huge improvement.
Leo changes the theory and practice of literate programming as follows:

1. Leo reduces the need for comments within a program. In particular, bridge or transition phrases are almost always unnecessary in a literate outline. One never needs to say something like, "having just finished with topic x, we turn now to topic y." Literate outlines tend to be far less chatty than flat literate programs.

2. Leo shows that printed listings are not necessary to understand noweb or CWEB programs. No printed listing can be as clear as Leo's outline view. It is possible to make printed listings intelligible by adding "context lines" that show the outline context of a section definition. Without such context lines a literate program must rely on extensive bridge comments.

3. Leo shows that simple, standard, yet flexible outline organizations provide a more natural way to organize large programs than narrative organizations. Indeed, narrative style creates severe maintenance problems. The narrative is soon forgotten, and when that happens it becomes difficult to find anything. The few times I have tried narrative organization I soon regretted it: things just weren't where I expected them to be.

4. Leo shows that traditional literate programming encourages a too creative approach to programming. A dictionary is a better model for programs than a novel. Literate outlines provide a more regular organization, while providing space for the most lengthy discussions when those discussions are required.

In short, Leo shows that the phrase "literate programming" is a misnomer. Indeed, chunks or sections, the atoms of literate programming, have little to do with literature. Outlines eliminate the need for narrative. One can use chunks or sections to write narrative prose, but Leo demonstrates that one is under no obligation to do so.
@nocolor @language python

This chapter documents scripting for Leo.py. Scripting is always available for leo.py.

This chapter describes only the most useful functions, classes and methods in leo.py. However, keep in mind the following:

** Your scripts have complete access to _all_ of Leo's source code **

Therefore, the most complete documentation for scripting is Leo's source code itself, that is, the file LeoPy.leo. In particular, see the node called "Overview of the Code", the first child of the node called "Code".

You can execute many of the scripts in this chapter by selecting the script and executing the "Execute Script" command. Note that the print statement will send its output to the console window.
@nocolor Leo's source code is a collection classes, along with utility functions in leoGlobals.py.

The _application instance_ is a singleton class representing the entire Leo application. The ivars (instance variables) of this class represent Leo's global variables.

A _commander_ (an instance of the Commands class) represents the operations that can be performed on a _particular_ window. Each open Leo window has its own commander.

A _frame_ (an instance of the LeoFrame class) contains all the internal data needed to manage a Leo window.

A _vnode_ represents a headline and its associated body text. vnode methods get and set headline text, body text and properties.

All these classes have ivars that allow code to find all the other classes. The top() and app() utility functions allow you to find the application instance and the commander of the topmost window.

You can execute this script right now by selecting the script and choosing the "Execute Script" command! The output from the print command will go to the console. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import app,top a=app() # The singleton instance. windows = a.windowList # The list of all open _frames_ for w in windows: print w print w.commands print w.commands.rootVnode()
@nocolor The top() function returns the Commands object of the top Leo window. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top c = top() print c print c.rootVnode()

@nocolor The windowlist attribute of the application instance contains the list of the frames of all open windows. The commands ivar of the frame gives the commander for that frame. Execute this code now with the Execute Script command. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import app a = app() # get the singleton application instance. windows = a.windowList # get the list of all open frames. for f in windows: c = f.commands print print "frame:", f print "file name:", f.shortFileName() print "commander:", c print "root vnode:", c.rootVnode()
@nocolor leoGlobal.py defines 6 convenience methods for redirecting stdout and stderr:

- redirectStderr() # Redirect stderr to the current log pane. - redirectStdout() # Redirect stdout to the current log pane. - restoreStderr() # Restores stderr so it prints to the console window. - restoreStdout() # Restores stdout so it prints to the console window. - stdErrIsRedirected() # Returns true if the stderr stream is redirected to the log pane. - stdOutIsRedirected() # Returns true if the stdout stream is redirected to the log pane.

Calls need _not_ be paired. Redundant calls are ignored and the last call made controls where output for each stream goes. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import stdErrIsRedirected,stdOutIsRedirected print "stdout isRedirected:", stdOutIsRedirected() print "stderr isRedirected:", stdErrIsRedirected()

from leoGlobals import redirectStderr redirectStderr()

from leoGlobals import stdErrIsRedirected,stdOutIsRedirected print "stdout isRedirected:", stdOutIsRedirected() print "stderr isRedirected:", stdErrIsRedirected()

from leoGlobals import redirectStdout redirectStdout()

from leoGlobals import stdErrIsRedirected,stdOutIsRedirected print "stdout isRedirected:", stdOutIsRedirected() print "stderr isRedirected:", stdErrIsRedirected()

from leoGlobals import restoreStderr restoreStderr()

from leoGlobals import stdErrIsRedirected,stdOutIsRedirected print "stdout isRedirected:", stdOutIsRedirected() print "stderr isRedirected:", stdErrIsRedirected()

from leoGlobals import restoreStdout restoreStdout()

from leoGlobals import stdErrIsRedirected,stdOutIsRedirected print "stdout isRedirected:", stdOutIsRedirected() print "stderr isRedirected:", stdErrIsRedirected()

@nocolor It may be convenient to redirect output in a hook. This can be done as follows: @color

if tag == "start1": from leoGlobals import redirectStdout,redirectStderr redirectStdout() # Redirect stdout redirectStderr() # Redirect stderr
@nocolor The following script shows how to access the data of a Leo window. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top c=top() # get the commander for the topmost outline window. v=c.currentVnode() # get the current vnode. print v print "head:", v.headString() print "body:", v.bodyString()
@nocolor The following routines will work whether or not v is the current vnode. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top c = top() ; v = c.currentVnode()

body = v.bodyString() print "body:", body head = v.headString() print "head:", head

# This won't really anyting because we haven changed body or head. v.setBodyStringOrPane(body) # set body text of v to s. v.setHeadStringOrHeadline(head) # set headline text of v to s.
@nocolor The following shows how to access all the vnodes of an outline in order. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top v=top().rootVnode() while v: # ...do something with v... print v v=v.threadNext()
@nocolor All of Leo's commands update the screen so as to eliminate flicker. That is, commands cause the outline pane to be redrawn only once. This is typically done using c.beginUpdate() and c.endUpdate().

c.beginUpdate() suppresses all drawing in the outline pane until the matching c.endUpdate() is seen. These methods can be nested; each c.beginUpdate() must have a corresponding c.endUpdate(). c.endUpdate takes an optional parameter. If this parameter is false, no redrawing is done. This can sometimes be useful; inner code can tell other code whether redrawing is actually required.

c.redraw() forces an update of the entire screen. c.endUpdate() calls c.redraw() as needed. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top c = top() # Force a screen redraw c.redraw() # Typical way to redraw the screen: c.beginUpdate() # ...code that alters the screen... c.endUpdate() # Typical way to conditionally redraw the screen: c.beginUpdate() # ...code sets flag if the screen needs redrawing. flag = 0 c.endUpdate(flag)
@nocolor Leo dispatches commands using the frame.doCommand and "Menu Command Handlers" defined in the node by the same name in leoFrame.py. This node contains the menu handlers for _all_ of Leo's menu commands, organized by menu and submenu.

I recommend that your scripts use frame.doCommand for the following reasons:

- frame.doCommand calls the "command1" and "command2" hook routines for the given label and - frame.doCommand catches all exceptions thrown by the command. - the menu commands handles hide a number of implementation details.

For example, try executing this code using the "Execute Script" command: @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top c = top() ; f = c.frame f.doCommand(f.OnMark,label="markheadline") # Executes command and calls hooks.

@nocolor Your scripts may also call command methods directly, like this: @color

from leoGlobals import top top().markHeadline() # Executes the command without calling hooks.

@nocolor As stated above, the commands handlers in leoFrame.py hide many implementation details involved in involking the various commands. If you want to invoke commands without calling frame.doCommand, you should study the corresponding menu command handler to see what may be required.

Among these details is this: commanders create subcommanders to handle complex commands:

- The atFile class (leoAtFile.py) reads and writes files derived from @file trees. - The LeoFind class (leoFind.py) handles the Find and Change commands. - The leoImportCommands class (leoImport.py) handles the Import and Export commands. - The tangleCommands class (leoTangle.py) handles the Tangle and Untangle commands. - The undoer class (leoUndo.py) handles the Undo command. - All other command are handled by the commander itself (leoCommands.py)
@nocolor Your scripts may also get one of the following prefs ivars in a commander.

c.tangle_batch_flag c.untangle_batch_flag c.use_header_flag c.output_doc_flag c.page_width c.tab_width c.tangle_directory c.target_language

LeoPrefs.ivars is a list of the _names_ of these ivars.

Each commander maintains its own preferences. The prefs panel updates its commander's ivars as needed. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top from leoPrefs import ivars # Get list of ivars that exist in LeoPrefs class and Commands class. c = top() print "\n\nPrefs ivars...\n" for ivar in ivars: exec("val=c."+ivar) # do val = c.ivar print "c.%-20s = %s" % (ivar,`val`)
@nocolor Your scripts may also set preferences by setting one of the following commands ivars: c.tangle_batch_flag, c.untangle_batch_flag, c.use_header_flag, c.output_doc_flag, c.page_width, c.tab_width, c.tangle_directory, c.target_language.

If the prefs panel is open, your script may call f.prefsPanel.init(c) to update the corresponding prefs panel ivars. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top c = top() ; f = c.frame c.tab_width = 4 # Change this and see what happens. # If the prefs panel is open, your script may call f.prefsPanel.init(c) to update it. if f.prefsPanel: f.prefsPanel.init(c) # If your script sets c.tab_width your script may call f.setTabWidth to redraw the screen. f.setTabWidth(c.tab_width)
@nocolor The file leoFindScript.py contains functions for finding and changing text from within scripts. The children of this node show two examples of using the functions. See the @file leoFindScript.py tree in LeoPy.leo for full details. @color
@nocolor The findall function returns a list of tuples (v,pos) describing matches in c's entire tree. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top,get_line_after from leoFindScript import findAll

pattern="from leoGlobals import" result = findAll(top(),pattern,bodyFlag=1)

print "\n\n%-3d instances of: '%s'...\n" % (len(result),pattern) for v,pos in result: body = v.bodyString() print '\n%-4d %s' % (pos,v.headString()) print get_line_after(body,pos)
@nocolor The reFindall function returns a list of tuples (v,mo,pos), where mo is a "Match Object". The reFlags argument are flags to re.search() @color

# You may execute this code with the "Execute Script" command... from leoGlobals import top,get_line_after from leoFindScript import reFindAll

pattern="from .* import" result = reFindAll(top(),pattern,bodyFlag=1,reFlags=None)

print "\n\n%-3d instances of: '%s'...\n" % (len(result),pattern) for v,mo,pos in result: body = v.bodyString() print '\n%-4d %s' % (pos,v.headString()) print get_line_after(body,pos)
@nocolor Most scripts will use the vnode class methods. The following section summarizes the most useful methods that your scripts can use. For a complete list, see the @file leoNodes.py tree of LeoPy.leo. @color
@nocolor This section lists the most useful vnode methods for getting information from vnodes. @color

# Returning commanders... v.commands() # The commander for v.

# Returning vnodes...These may return None. v.next() # the previous sibling of v. v.back() # the next sibling of v. v.firstChild() # the first child of v. v.lastChild() # the last child of v. v.lastNode() # the last node of the subtree of which v is the root. v.nodeAfterTree() # v.lastNode.threadNext v.nthChild(n) # the n'th child of v, starting at index 0. v.parent() # the parent node of v. Level 0 nodes have no parent. v.threadBack() # the node before v. v.threadNext() # the node after v. v.visBack() # the visible node before v. v.visNext() # the visible node after v.

# Returning strings... v.bodyString() # the body string of v. v.headString() # the headline string of v.

# Returning ints... v.childIndex() v.numberOfChildren() v.level()

# Returning bools representing property bits... v.hasChildren() v.isAncestorOf(v2) # true if v2 is a child, grandchild, etc. of v. v.isCloned() v.isDirty() v.isExpanded() v.isMarked() v.isVisible() v.isVisited()
@nocolor This section lists the most useful vnode methods that set vnode data or properties. @color

# Convenience methods for altering headline or body text... # These work whether or not v is the presently selected node. v.setBodyTextOrPane(s) # Sets the body text of v. v.setHeadStringOrHeadline(s) # Sets the headline text of v.

# Moving nodes... v.moveAfter(v2) # move v after v2 v.moveToNthChildOf(v2,n) # move v to the n'th child of v2 v.moveToRoot() # make v the root vnode

# The "visited bit" may be used by commands or scripts for any purpose. # Many commands use this bits for tree traversal, so these bits do not persist. c.clearAllVisited() # Clears all visited bits in c's tree. v.clearVisited() v.setVisited()
@nocolor Here is an example code that uses vnode methods. @color

# You may execute this code with the "Execute Script" command...

# Returns a list of v's children def children (v): result = [] child = v.firstChild() while child: result.append(child) child=child.next() return result

# Test code. from leoGlobals import top c=top() ; v = c.currentVnode() siblings = children(v.parent()) for sib in siblings: print sib
@nocolor The file leoGlobals.py contains a large number of "utility" functions. For a list of all these functions, do the following: @color

# You may execute this code with the "Execute Script" command...

# Print a list of symbols defined in leoGlobals. import leoGlobals print "\n\nNames defined in leoGlobals.py...\n" d = leoGlobals.__dict__ names = d.keys() names.sort() for name in names: print name

@nocolor Unless you are familiar with the names of all these functions it may be more comfortable to import just the names you want, like this: @color

from leoGlobals import app,es,top # And whatever else you want to use.

@nocolor We have already discussed the app() and top() functions.

true and false are synonyms for True and False (Python 2.2 and later) or 1 and 0 (before Python 2.2).

The es(s) function puts s+'\n' to the log pane. es(s,newline=false) puts s to the log pane. @color

# You may execute this code with the "Execute Script" command... from leoGlobals import es,false es("Hello World") es("Hello",newline=false) ; es(" World")

@nocolor For complete details of all routines defined in leoGlobals.py, see LeoPy.leo.
@nocolor @language python

This appendix discusses how you can execute Python scripts in the Borland version of Leo. It lists all the Python functions, classes and methods that are available in the Borland version of Leo. The Borland version of Leo creates these using an extension module; they don't happen naturally as is the case in leo.py.

This appendix uses the same terminology and conventions as does Chapter 7 itself. That is, c represents a commander, v represents a vnode, and s represents a Python string. In addition, b represents a boolean value.

For leo.py:

leo.py requires Python 2.1 or later. Scripting is always available for leo.py.

For Borland versions of Leo:

To use Leo's Python window you must install Python 1.5.2 or later. Python's installer will place the Python DLL where Leo will find it. Leo's Python window has been tested only with Python 1.5.2. It's not clear whether later versions will work.

To open the Python window the following files should exist in the same directory as leo.exe. These files are part of both the leoNN.zip and leosrcNN.zip. Note: leosrc.leo is the primary source for these .py files.

leo.py Makes Leo's commands and data available to Python. leoEval.py Manages Leo's Python window. wxdb.py Runs the pdb debugger from Leo's Python window. sitecustomize.py Optional site-specific customization called by ..\Python\Lib\site.py.
Executing the "Open Python Window" command from Leo's Window menu brings up a window with two panes. You enter Python source code in the top pane. Pressing return executes the code in the top pane. Results are sent to the bottom pane.

To run the standard pdb debugger inside Leo:

1. import wxdb

2. call the wxdb.run, wxdb.runeval and wxdb.runcall routines. Output goes to the output pane. Input comes from a modal dialog. The only way to close this dialog is by typing the quit.
c = leo.topCommand()

The topCommand() function returns the Commands object of the top Leo window.

For example:

import leo c=leo.topCommand() v=c.rootVnode()
@nocolor

[c] = leo.getCommands()

The clist=getCommands() function returns a list of commanders for all open windows.

clist[0] is the commander for the main window, so clist[0].rootVnode() will be None. Use clist[1:] to get the list of all outline windows. For example:

@color

import leo clist=leo.getCommands() for c in clist[1:]: root=c.rootVnode() name=c.fileName() print 'The root of ', name, ' is ', root.headString()
w = leo.getLeoPyWindow()

The getLeoPyWindow() method returns the interface class for Leo's Python window. This class contains the following routines:

w.read() w.readline() w.setStatus() w.write()
@nocolor

The commands class represents the commands for a _particular_ open Leo window. The following description is organized by the menus in Leo's outline window. All commands return None; use Commands methods to determine whether an operation may take place _before_ attempting the operation.

The leo.topCommand() function returns the commander for the topmost outline window. The leo.Commands() function returns a list of commanders for all open outline windows.

The Commands class provides almost all the operations for creating, deleting, moving, cloning vnodes. The vnode class provides getters for extracting information from vnodes and setters for changing some aspects of vnodes. When there is a choice, you should use commands in the Commands class; they are safer.

Scripts have no access to Leo's interactive Find/Change commands. It is easy to write scripts that mimic these commands. Indeed, these scripts will have the full power of Python's patter matching at their disposal.

@color
@nocolor

c.BeginUpdate() suppresses all drawing until the matching c.EndUpdate() is seen. These methods can be nested and each c.BeginUpdate() must have a corresponding c.EndUpdate(). Wrapping code in calls to c.BeginUpdate() and c.EndUpdate() eliminates screen flicker.

c.Repaint() forces an update of the entire screen. This should seldom be necessary since all commands update the screen properly.

c.EndUpdate() calls c.Repaint(), so the typical way to update the screen is simply:

@color @code c.BeginUpdate() # code that alters the screen. c.EndUpdate()
@code

c.new(windowName) b=c.open(fileName) # returns TRUE if file could be opened or was already open. # brings the window to the front if it has already been opened. c.close() b=c.save(fileName) b=c.saveAs(fileName) b=c.saveACopyAs(fileName) c.revert(windowName)

c.tangle() c.tangleAll() c.tangleMarked()

c.untangle() c.untangleAll() c.untangleMarked()

# fileNames is a list of file names

c.flattenOutline(fileNames) c.cwebToOutline(fileNames) c.importFiles(fileNames) c.importMoreText(fileNames)

c.exportMoreText() # copies MORE format text to the clipboard
@code

c.cut c.copy c.paste c.delete c.selectAll

c.editCurrentHeadline()

c.extract() c.extractSection() c.extractSectionNames() c.convertBlanks() c.ReformatParagraph()
@code

c.cutOutline() c.copyOutline() c.pasteOutline() c.deleteHeadline() c.insertHeadline() c.clone()

c.contractSubheads() c.contractAllSubheads() c.contractAllHeadlines()

c.expandAllHeadlines() c.expandAllSubheads() c.expandSubheads()

c.expandLevel1() c.expandLevel2() c.expandLevel3() c.expandLevel4() c.expandLevel5() c.expandLevel6() c.expandLevel7() c.expandLevel8() c.expandLevel9() c.expandNextLevel()

c.moveOutlineLeft() c.moveOutlineRight() c.moveOutlineUp() c.moveOutlineDown()

c.promote() c.demote()

c.selectThreadBack() c.selectThreadNext() c.selectVisBack() c.selectVisNext()

c.markHeadline() c.markSubheads() c.markChangedHeadlines() c.markChangedRoots() c.unmarkAll()

c.goToNextDirtyHeadline() c.goToNextMarkedHeadline()
@code

c.equalSizedPanes()
@nocolor

The following routines all return true if the corresponding Menu command should be enabled. The actual menu commands themselves silently do nothing if they are called when the corresponding canXXX routines returns false.

@color

b=canContractAllHeadlines() b=canContractAllSubheads() b=canContractSubheads() b=canCutOutline() b=canDeleteHeadline() b=canDemote() b=canExpandAllHeadlines() b=canExpandAllSubheads() b=canExpandSubheads() b=canExtractSection() b=canExtractSectionNames() b=canGoToNextDirtyHeadline () b=canGoToNextMarkedHeadline() b=canMarkChangedHeadlines() b=canMarkChangedRoots() b=canMoveOutlineDown() b=canMoveOutlineLeft() b=canMoveOutlineRight() b=canMoveOutlineUp() b=canPasteOutline() b=canPromote() b=canRevert() b=canSelectVisBack() b=canSelectVisNext() b=canSelectThreadBack() b=canSelectThreadNext() b=canSelectToEnd() b=canSelectToStart() b=canShiftBodyLeft() b=canShiftBodyRight() b=canUndo() b=canUnmarkAll()
@nocolor These utilities are important. There is no need to call them when using Leo's own commands, and they will come in handy when creating your own commands. @color

Drawing utilities...

c.BeginUpdate() # suppress screen updates. c.EndUpdate() # enable screen updates and redraw the screen. c.Repaint() # force the screen to be redrawn

@nocolor Enclose code in BeginUpdate/EndUpdate pairs to inhibit drawing while the screen is being changed. BeginUpdate/EndUpdate may be nested and each BeginUpdate must be matched with a corresponding EndUpdate call. EndUpdate calls Repaint automatically. You can call Repaint to force the screen to be updated immediately though all commands repaint the screen properly. c.bringToFront() brings the window corresponding to the command class to the front. A later leo.topCommand() will return c. @color

Getters...

v=c.currentVnode() # The currently selected vnode or None. v=c.rootVnode() # The first vnode of the outline or None. b=c.isChanged() # True if the outline has been changed since it was saved. s=c.fileName() # The file to which the Save command will write the window. May be "Untitled"

Setters...

c.bringToFront() # Bring the window to the front. see leo.topCommand(). c.clearAllVisited() # Clear all "visited" bits. c.editVnode(v) # Enter editing mode for v's headline. c.endEditing(v) # End editing mode for v's headline. c.makeVisible(v) # Expand outline if necessary to make v visible. c.selectVnode(v) # Make v the presently selected vnode. c.synchVnode() # No longer used. Retained for compatibility with old scripts.
@nocolor

This class exists to handle the interaction between the Python interpreter and Leo's Python window. Scripts would typically not use this class.

@color

w=leo.getLeoPyWindow() # get the interaction class. s=w.read() # Return what the user has _already_ typed. # This is used to start the Python interpreter s=w.readline() # Put up a modal dialog to get input and return what the user types. # It is not possible to break out of this dialog. # This routine is called by the pdb debugger. w.setStatus(n) # Called by leoEval.py to control how Leo's Python window works. # n=0: normal input of Python text. # n=1: continuation input. # n=2: immediate exit (not used). # n=3: call readline to get stdin input. w.write(s) # Writes s to the output pane of the Python window.
@nocolor

This class provides access to the Preference settings and the contents of the log window.

@color

Getters...

p = leo.getPrefs() # returns instance of the Prefs class

b=p.defaultTangleDirectory() b=p.doLeoDoneBat() b=p.doLeoUnBat() s=p.logWindowString() # returns contents of log window n=p.pageWidth() b=p.tangleOutputsHeaderLine() b=p.tangleOutputsDocChunks()

Setters...

p.setDefaultTangleDirectory(s) p.setDoLeoDoneBat(b) p.setDoLeoUnBat(b) p.setPageWidth(n) p.setTangleOutputsHeaderLine(b) p.setTangleOutputsDocChunks(b)
@nocolor

The vnode class represents a node containing headline text, body text and various properties.

@color
@nocolor

This section lists all the methods that return data and properties of vnodes.

Returning commanders...

The following method is quite handy. It allows us to avoid passing commanders as arguments to vnode methods. For example, we can call v.commands().synchNode()

@color

c=v.commands() # The commands object for the window containing v.

@nocolor

Returning vnodes... These may return None. Be warned.

@color

v=v.next() # the previous sibling of v. v=v.back() # the next sibling of v. v=v.currentVnode() # the presently selected vnode. v=v.findRoot() # the root vnode. v=v.firstChild() # the first child of v. v=v.lastChild() # the last child of v. v=v.lastNode() # the last node of the subtree of which v is the root. v=v.nodeAfterTree() # v.lastNode.threadNext v=v.nthChild(n) # the n'th child of v, starting at index 0. v=v.parent() # the parent node of v. Level 0 nodes have no parent. v=v.threadBack() # the node before v. v=v.threadNext() # the node after v. v=v.visBack() # the visible node before v. v=v.visNext() # the visible node after v.

Returning Python strings...

s=v.bodyString() # the body string of v. # Warning: this string may not match the body text if # v is the current vnode. Call c.synchVnode to make sure it does. s=v.convertTreeToString() # Converts v's subtree to an ascii string. s=v.headString() # the headline string of v.

# These two are of limited use: they are called by c.convertTreeToString(). s=v.moreBody() # the body text of v in MORE format. s=v.moreHead(n) # n = the first level of the converted string

Returning Python (short) ints...

n=v.childIndex() n=v.numberOfChildren() n=v.level()

@nocolor

Returning bools representing property bits... Only the "visited" bit may be altered directly by scripts.

@color

b=v.hasChildren() b=v.isAncestorOf(v2) # true if v2 is a child, grandchild, etc. of v. b=v.isCloned() b=v.isDirty() b=v.isExpanded() b=v.isMarked() b=v.isVisible() b=v.isVisited()
@nocolor

This section lists all vnode methods that set vnode data or properties.

Altering headline or body text...

@color

v.setBodyTextOrPane(s) # Sets the body text of v, whether or not v is the current node. v.setHeadStringOrHeadline(s) # Replaces the headline text of v, whether or not v is the current node. v.trimTrailingLines() # Removes all blanks lines from v. # Warning: will not work if v is the current vnode.

Moving nodes..

v.moveAfter(v2) # move v after v2 v.moveToNthChildOf(v2,n) # move v to the n'th child of v2 v.moveToRoot() # make v the root vnode

@nocolor

Redraws v's icon. This is usually done automatically.

v.setIcon()

The "visited bit" may be used by commands or scripts for any purpose. Commands use this bits for tree traversal. See also: c.clearAllVisited()

v.clearVisited() v.setVisited()
This chapter discusses the history of Leo and its various incarnations. A large part of this chapter discusses the process by which I discovered how to implement @file trees.
Leo grew out of my efforts to use Donald Knuth's "CWEB system of Structured documentation." I had known of literate programming since the mid 1980's, but I never understood how to make it work for me: I was confused about when to create new sections and it was difficult to keep track of all the sections once they were created.

In November 1995 I started thinking about literate programming in earnest. Over the holidays I mused about making literate programs more understandable. In January 1996 the fog of confusion suddenly cleared. I summarized my thinking with the phrase, "Webs are outlines in disguise". I strongly suspected that outline views were the key to literate programming, but many details remained obscure. It still wasn't clear exactly how to represent webs in an outline.

March 5, 1996, is the most important date in Leo's history. While returning from a day of skiing, I discussed my thoughts with Rebecca. During that conversation I realized that I could use the MORE outliner as a prototype for a "literate outliner." I immediately started work on my first literate outline. It quickly became apparent that outlines work: all my old problems with literate programming vanished. Furthermore, I realized that MORE's outlines could form the basis for Leo's screen design. Rather than opening body text within the outline, as MORE does, I decided to use a separate body pane.

In about a week I hacked a translator called M2Cwhich allowed me to use MORE to write real code. I would write code in MORE, copy the text to the clipboard in MORE format, then run M2C, which would tangle the outline into C code. This process was useful, if clumsy. I called the language used in the outline SWEB, for simplified CWEB. Much later I converted to the noweb language. noweb is simpler than SWEB, more standard and more general.
Throughout 1996 I created a version of Leo on the Macintosh in plain C and the native Mac Toolbox. This was a poor choice; I wasted a huge amount of time programming with these primitive tools. However, this effort convinced me that Leo was a great way to program.

Late in 1997 I wrote a Print command to typeset an outline. Printing (Weaving) is supposedly a key feature of literate programming. Imagine my surprise when I realized that such a "beautiful" program listing was almost unintelligible; all the structure inherent in the outline was lost! I saw clearly that typesetting, no matter how well done, is no substitute for explicit structure.

Starting in May of 1998 I began work on a version of Leo using objective C and Apple's Yellow Box environment. In some ways, this was the most powerful version of Leo. Alas, a year later Apple chose not to support Yellow Box on Windows, so I had to begin again. I was most unhappy.

In May of 1999 I began work on the Borland version of Leo for Windows. This version was successful, and I continue to support the Windows/Borland version to the present day. The Borland Delphi classes are a pleasure to use and free of bugs. I redesigned Leo's file format for the Windows version of Leo; the Yellow Box file format is a binary format that requires the Yellow Box runtime. Fortunately, I choose to use XML for Leo's file format. I have Marc-Antoine Parent to thank for this decision; he urged me to use XML and patiently explained how to use XML properly. However, there are two significant problems with the Borland version of Leo. First, it works only on Windows. Second, it can never be Open software, because it uses Borland's Delphi classes and a commercial syntax coloring component.

In May of 2000 I began work an wxWindows version of Leo. This was supposed to be a cross-platform version. Alas, after much work I chose to abandon the wxWindows version. There were too many bugs and too many incompatibilities between platforms. Something good did come from this effort. I spent a lot of time adding Python scripting to the wxWindows code and I became familiar with Python and its internals.

In October of 2001 I began work on the leo.py, an Open Software version of leo.py, a version of Leo written in Python and Tk. At last I have found the proper platform for Leo. leo.py naturally supports scripting in Python. The combination of Python and Tk is incredibly powerful, very easy to use, and truly cross platform. I rewrote Leo in Python in about two months! For the first time in my career I no longer am anxious while programming; it simply isn't possible to create bad bugs in Python.
The following sections give a pseudo-chronological list of the major Aha's involved in creating Leo2. These Aha's form the real design and theory of operation of Leo. See the "Diary", "Notes" and "Letters to Speed Ream" sections in LeoDocs.leo for a more accurate and less tidy history of Leo2.

I am writing these notes for several reasons. First, the initial design and coding of Leo2, spanning a period of about 8 weeks, was some of the most creative and rewarding work I have ever done. The result is elegant and simple. I'm proud of it. Second, much of the design work is not reflected in the code, because improved design often eliminated code entirely. The final code is so elegant that it obscures the hard work that created it. Third, you must understand this design in order to understand the implementation of @file trees and their derived files. Someday someone else may take charge of Leo. That person should know what really makes Leo2 work.
In the summer of 2001 I began work on a project that for a long time I had considered impossible. I had long considered that "private" file formats such as .leo files were the only way to represent an outline properly and safely. I'm not sure exactly what changed my mind, but I finally was willing to consider that information embedded in derived files might be useful. This meant accepting the possibility that sentinel lines might be corrupted. This was a crucial first step. If we can trust the user not to corrupt sentinel lines than we can embed almost any kind of information into a derived file.

There were several motivations for this work. I wanted to eliminate the need for explicit Tangle and Untangle commands. I thought of this as "Untangle on Read/Tangle on Write." If tangling and untangling could be made automatic it would save the user a lot of work. I also wanted to make derived files the primary sources files. .leo files might be made much smaller derived files contained the primary source information. This hope turned out to be false.

The result of this design work was something I originally called Leo2, though I now usually prefer to talk about @file trees. Initially most design issues were unresolved or unknown. I resolved to attempt a robust error-recovery scheme, not knowing in advance what that might involve. I also wanted to solve what I thought of as the "cross-file clone" problem: clones that point from a .leo outline into a derived file. With Leo1 cross-file clones do not exist; everything is in the same .leo file. It was clear that Leo2 would have to change some aspects of clones, but all details were fuzzy.
The next step was also crucial. I started to use Leo1 as a prototype to design what the new body pane would look like to the user. In retrospect, using Leo1 as a prototype for Leo2 was just as inspired as using MORE as a prototype for Leo1. Both prototypes marked the true beginning of their respective projects. The Leo2 prototype was a mockup in Python of the code for reading and writing derived files. The file LeoDocs.leo contain these first prototype nodes.

Writing the prototype got me thinking about improving noweb. With my experience with Leo1, I was able to create a new markup language that took advantage of outline structure. I called the new language "simplified noweb", though that terminology is obsolete. I created @file nodes to distinguish between the old and new ways of creating derived files. In Leo1, the @code directive is simply an abbreviation for a section definition line. Simplified noweb used @c as an abbreviation for @code. More importantly, simplified noweb used @c to separate doc parts from code parts without necessarily specifying a section name. It quickly became apparent that most nodes could be unnamed. All I needed was the @others directive to specify the location for all such unnamed nodes.

From the start, simplified noweb was a joy to use. Indeed, the @others directive could replace all section definition lines. Furthermore, I could make @doc directive optional if the body pane started in "code mode". But this meant that plain body text could become a "literate" program! This was an amazing discovery. These Aha's got me excited about Leo2. This was important, as it motivated me to do a lot of difficult design work.
In spite of this excitement, I was uneasy. After much "daydreaming" I realized that I was afraid that reading and writing derived files would be interrupted by a long series of alerts. I saw that designing the "user interaction" during reading and writing would be very important. The next Aha was that I could replace a long series of alerts with messages to the log window, followed by a single "summary" alert. Much later I saw how to eliminate alerts entirely.

At this time I thought there would be two kinds of "errors" while reading derived files. Warnings would alert the user that something non-serious had happened. True errors would alert the user that data might have been lost. Indeed, if Leo2 saves orphan and ignored nodes in a .leo file under an @file node, then read errors could endanger such nodes. Much later I saw that a robust error recovery scheme demands that @file nodes not contain orphan and @ignored nodes. (More on this subject later.) But if orphan and @ignored nodes are moved out of @file trees, there are no read errors that can cause data loss! So the distinction between warnings and errors finally went away.
I next turned my attention to writing @file nodes. A huge Aha: I realized that sentinel lines must contain both a leading and a trailing newline. The general principle is this: the write code must contain absolutely no "conditional" logic, because otherwise the read code could not figure out whether the condition should be true or false. So derived files contain blank lines between sentinel lines. These "extra" newlines are very useful, because the read (untangle) code can now easily determine exactly where every blank, tab and newline of the derived file came from. It would be hard to overstate how important this simplifying principle was in practice.

Much later, with urging from a customer, I realized that the write code could safely remove "extra" newlines between sentinels with a caching scheme in the low level atFile::os() routine. This scheme does not alter the body of the write code in any way: in effect, sentinels still contain leading and trailing "logical" newlines. The read code had to be modified to handle "missing" leading newlines, but this can always be done assuming that sentinels still contain logical leading and trailing newlines!

At about this time I designed a clever way of having the write code tell the read code which newlines were inserted in doc parts. (The whole point of doc parts is to have the write code format long comments by splitting long lines.) To quote from my diary:

"We can use the following convention to determine where putDocPart has inserted line breaks: A line in a doc part is followed by an inserted newline if and only if the newline is preceded by whitespace. This is a really elegant convention, and is essentially invisible to the user. Tangle outputs words until the line would become too long, and then it inserts a newline. To preserve all whitespace, tangle always includes the whitespace that terminates a word on the same line as the word itself. Therefore, split lines always end in whitespace. To make this convention work, tangle only has to delete the trailing whitespace of all lines that are followed by a 'real' newline."

After the write code was working I turned my attention to the read (untangle) code. Leo's Untangle command is the most complex and difficult code I have ever written. Imagine my surprise when I realized that the Leo2 read code is essentially trivial! Indeed, the Leo2 untangle code is like an assembler. The read code scans lines of a derived files looking for "opcodes", that is, sentinel lines, and executes some simple code for each separate opcode. The heart of this code is the scanText routine in atFile.cpp.

The read code was written and debugged in less than two days! It is the most elegant code I have ever written. While perfecting the read code I realized that sentinel lines should show the complete nesting structure found in the outline, even if this information seems redundant. For example, I was tempted to use a single sentinel to represent an @other directive, but finally abandoned this plan in favor of the @+other and @-other sentinels.

This redundancy greatly simplified the read code and made the structure of derived files absolutely clear. Moreover, it turned out that we need, in general, all the information created by the present sentinel lines. In short, sentinels are as simple as they can be, and no simpler.

The atFile::createNthChild method is a very important: it ensures that nodes will be correctly inserted into the outline. createNthChild must be bullet-proof if the Read code is to be robust. Note that the write code outputs @node sentinels, that is, section definitions, in the order in which sections are referenced in the outline, not the order in which sections appear in the outline. So createNthChild must insert the n'th node of parent p properly even if p contains fewer than n-1 children! The write code ensures that section references are properly nested: @node sentinels are enclosed in @node sentinels for all their ancestors in the @file tree. createNthChild creates dummy siblings as needed, then replaces the dummy siblings later when their actual definitions, that is, @node sentinels, are encountered.

At this point the fundamental read/write code was complete. I found three minor bugs in the code over the next week or so, but it was clear that the read/write code formed a rock-solid base from which to continue design and implementation. This was an entirely unexpected surprise.
At this point I could read and write derived files "by hand", using temporary Read and Write commands. The next step was to integrate the reading and writing of derived files with the loading and saving of .leo files. From time to time I made minor changes to the drivers for the read/write code to accommodate the Load and Save code, but at no time did I significantly alter the read or write code itself.

The user interaction of the Load and Save commands drove the design and implementation of the load/store code. The most important questions were: "what do we tell the user?", and "what does the user do with the information?" It turns out that the user can't make any complex decision during error recovery because the user doesn't have nearly enough information to make an informed choice. In turn, this means that certain kinds of error recovery schemes are out of the question...
I now turned my attention to "attributes" of nodes. Most attributes, like user marks, are non-essential. However, clone information is essential; we must never lose clone links. At this time I had a preliminary design for cross-file clones that involved a two part "pointer" consisting of a full path name and an immutable clone index within the derived file. Eventually such pointers completely disappeared, but the immutable clone indices remain.

My first thought was that it would be good to store all attributes in @node sentinels in the derived file, but experience showed that would be irritating. Indeed, one wants Leo2 to rewrite derived files only if something essential has changed. For example, one doesn't want to rewrite the derived file just because a different node as been selected.

At this point I had another Aha: we can use the .leo file to store all non-essential attributes. For example, this means that the .leo file, not the derived files, will change if we select a new node. In effect, the .leo file mirrors the derived file. The only reason to store nodes in the .leo file under an @file node is to carry these attributes, so Leo2 wrote dummy nodes that do not reference body text. Much later I saw that dummy nodes were dangerous and that .leo files should contain all information found in derived files.
The concept of mirroring created a huge breakthrough with cross-file clones: Here is an excerpt of an email i sent to my brother Speed:

"I realized this morning that since a .leo file contains dummy vnodes for all nodes in a derived file, those dummy nodes can carry clone info! I changed one line to make sure that the write code always writes clone info in dummy vnodes and voila! Cross-file clones worked!"

All of Leo1's clone code could be used completely unchanged. Everything "just works".
At first I thought we could make sure that the .leo file always correctly mirrors all derived file, but disastrous experience showed that is a completely false hope. Indeed, backup .leo files will almost never mirror derived file correctly. So it became urgent to find a completely fool-proof error recovery scheme.

I had known for quite a while that error recovery should work "as if" the mirroring nodes were deleted, then recreated afresh. Several failed attempts at an error recovery scheme convinced me that error recovery would actually have to delete all dummy nodes and then do a complete reread. This is what Leo2 does.

But erasing dummy nodes would destroy any orphan and ignored nodes--by definition such nodes appear nowhere in the derived file. Therefore, I had to enforce the rule that @file nodes should contain no such nodes. Here is an email I wrote to my brother, Speed Ream discussing what turned out to be the penultimate error recovery scheme:

"The error recovery saga continues. After much pondering and some trial coding I have changed my mind about orphans and @ignored nodes. They simply should never appear as descendants of @file nodes. Fortunately, this simplifies all aspects of Leo2. Leo2 will issue a warning (not an error) if an orphan or @ignored node appears as the descendant of an @file node when a .leo file is being saved. If any warnings occur while writing the derived file, Leo2 will write the "offending" @file tree to the .leo file instead of the derived file. This has several advantages:

1. The user gets warned about orphan nodes. These are useful warnings! Orphan nodes arise from missing @others directives or missing section references.

2. The user doesn't have to change anything immediately in order to save an outline. This is very important. Besides warnings about orphans, Leo2 will also warn about undefined or unreferenced sections. User's shouldn't have to fix these warnings to do a Save!

3. No errors or alerts will occur during Reading or Writing, so the user's anxiety level goes way down. At worst, some informational message will be sent to the log. The user will never have to make important decisions during Loads or Saves. [At last the dubious distinction between errors and warnings disappears.]

4. Error recovery can be bullet-proof. Simple code will guarantee that after any read operation the structure of an @file node will match the structure of the derived file. Also, sentinels in derived files will now account for all children of an @file node. There are no more "missing nodes" that must be filled in using the .leo file. Finally, error recovery will never change the @file tree in any way: no more "recovered nodes" nodes.

5. The present read code can be used almost unchanged. The only addition is the posting of a warning if the structure of the .leo file does not match the structure of the derived file. We need a warning because non-essential attribute of nodes (like user marks) may be altered."

This ends the original history of Leo2. In fact, it took quite a while before Leo recovered properly from all errors. I finally saw that .leo files should duplicate all information in derived files. This allows a .leo file to be used a single backup file and allows maximal error recovery in all situations. It took several months to stamp out several subtle bugs involving clones that caused spurious read errors. Such errors undermine confidence in Leo and can cause disastrous reversions. See my diary entries for January 2002 in leo.py for details.

This chapter discusses how Leo's code works, paying particular attention to topics that have caused difficulties in design or implementation. This information will be of use primarily to those wanting to change Leo's code. The previous chapter contains a full discussion of how Leo reads and writes @file trees.
This documentation describes leo.py. Other versions of Leo are similar in design; the differences between versions are generally not interesting enough to describe here.

All versions of Leo are organized as a collection of classes. The general organization of Leo has remained remarkably stable throughout all versions of Leo, although the names of classes are different in different versions. Smalltalk's Model/View/Controller terminology is a good way to organize Leo's classes conceptually.

Model classes represent the fundamental data. The vnode and tnode classes are Leo's primary model classes.

View classes draw the screen. The main view classes are leoFrame.py and leoTree.py. The colorizer class in leoColor.py handles syntax coloring in the body pane. In leo.py, the view classes know about data stored in the vnode class. Most events (keystrokes and mouse actions) in the outline and body pane are handled in the leoTree class. The leoFrame class also creates the Leo window, including menus, and dispatches the appropriate members of the controller classes in response to menu commands.

Controller classes (aka commanders) control the application. In Leo, controllers mostly handle menu commands. In leo.py, the Commands class creates subcommanders to handle complex commands. The atFile class reads and writes files derived from @file trees. The LeoFind class handles the Find and Change commands. The leoImportCommands class handles the Import and Export commands, the tangleCommands class handles the Tangle and Untangle commands and the undoer class handles the Undo command. Other classes could be considered controller classes.

Each Leo window has its own commander and subcommanders. Subcommanders are not subclasses of their commander. Instead, subcommanders know the commander that created them, and call that commander as needed. Commanders and subcommanders call the model and view classes as needed. For example, the Commands class handles outline commands. To move a headline, the commander for the window calls a vnode move routine to alter the data, then calls the view class to redraw the screen based on the new data.

A singleton instance of the LeoApp class represents the application itself. All code uses the app() global function to gain access to this singleton member. The ivars of the LeoApp object are the equivalent of Leo's global variables. leo.py uses no global Python variables, except the gApp variable returned by app(). leoGlobals.py defines all application constants. Naturally, most constants are local to the class that uses them.

Several classes combine aspects of model, view and controller. For example, the LeoPrefs class represents user preferences (model), the Preference Panel (view) and the Preferences menu command (controller). Similarly, the LeoFind class represents find settings, the Find/Change dialog, and the Find/Change commands.

We use the following convention throughout this documentation. Any variable named c is a commander, i.e., an instance of the Commands class in leoCommands.py. Variables named v and t are vnodes and tnodes respectively. These classes are defined in leoNode.py.
The vnode and tnode classes represent most of the data contained in the outline. These classes are Leo's fundamental Model classes.

A vnode (visual node) represents a headline at a particular location on the screen. When a headline is cloned, vnodes must be copied. vnodes persist even if they are not drawn on the screen. Commanders call vnode routines to insert, delete and move headlines.

The vnode contains data associated with a headline, except the body text data which is contained in tnodes. A vnode contains headline text, a link to its tnode and other information. In leo.py, vnodes contain structure links: parent, firstChild, next and back ivars. To insert, delete, move or clone a vnode the vnode class just alters those links. The Commands class calls the leoTree class to redraw the outline pane whenever it changes. The leoTree class knows about these structure links; in effect, the leoTree and vnode classes work together. The implementation of vnodes is quite different in the Borland version of Leo. This does not affect the rest of the Leo. Indeed, vnodes are designed to shield Leo from such implementation details.

A tnode, (text node) represents body text: a tnode is shared by all vnodes that are clones of each other. In other words, tnodes are the unit of sharing of body text. The tnode class is more private than the vnode class. Most commanders deal only with vnodes, though there are exceptions.

Because leo.py has unlimited Undo commands, vnodes and tnodes can be deleted only when the window containing them is closed. Nodes are deleted indirectly. Several classes, including the vnode, tnode, leoFrame and leoTree classes, have destroy() routines. These destroy() routines merely clear links so that Python's and Tkinter's reference counting mechanisms will eventually delete vnodes, tnodes and other data when a window closes.

Leo uses several kinds of node indices. Leo's XML file format uses tnode indices to indicate which tnodes (t elements) belong to which vnodes (v elements). Such indices are required. Even if we duplicated the body text of shared tnodes within the file, the file format would still need an unambiguous way to denote that tnodes are shared.

Present versions of Leo recompute these tnodes indices whenever Leo writes any .leo file. Earlier versions of Leo remembered tnode indices and rewrote the same indices whenever possible. Those versions of Leo recomputed indices when executing the Save As and Save To commands, so using these commands was a way of "compacting" indices. The main reason for not wanting to change tnode indices in .leo files was to reduce the number of changes reported by CVS and other Source Code Control Systems. I finally abandoned this goal in the interest of simplifying the code. Also, CVS will likely report many differences between two versions of the same .leo file, regardless of whether tnode indices are conserved.

A second kind of node index is the clone index used in @+node sentinels in files derived from @file trees. As with indices in .leo files, indices in derived files are required so that Leo can know unambiguously which nodes are cloned to each other.

It is imperative that clone indices be computed correctly, that is, that tnode @+node sentinels have the same index if and only if the corresponding vnodes are cloned. Early versions of leo.py had several bugs involving these clone indices. Such bugs are extremely serious because they corrupt the derived file and cause read errors when Leo reads the @file tree. Leo must guarantee that clone indices are always recomputed properly. This is not as simple as it might appear at first. In particular, Leo's commands must ensure that @file trees are marked dirty whenever any changed is made that affects cloned nodes within the tree. For example, a change made outside any @file tree may make several @file trees dirty if the change is made to a node with clones in those @file trees.
@nocolor

Leo must redraw the outline pane when commands are executed and as the result of mouse and keyboard events. The main challenges are eliminating flicker and handling events properly. These topics are interrelated.

Eliminating flicker. Leo must update the outline pane with minimum flicker. Various versions of Leo have approached this problem in different ways. The drawing code in leo.py is robust, flexible, relatively simple and should work in almost any conceivable environment.

Leo assumes that all code that changes the outline pane will be enclosed in matching calls to the c.beginUpdate and c.endUpdate methods of the Commands class. c.beginUpdate() inhibits drawing until the matching c.endUpdate(). These calls may be nested; only the outermost call to c.endUpdate() calls c.redraw() to force a redraw of the outline pane.

In leo.py, code may call c.endUpdate(flag) instead of c.endUpdate(). Leo redraws the screen only if flag is true. This allows code to suppress redrawing entirely when needed. For example, here is how the idle_body_key event handler in leoTree.py conditionally redraws the outline pane:

@language python @color redraw_flag = false c.beginUpdate() val = v.computeIcon() if val != v.iconVal: v.iconVal = val redraw_flag = true c.endUpdate(redraw_flag) # redraw only if necessary @nocolor

The leoTree class redraws all icons automatically when c.redraw() is called. This is a major simplification compared to previous versions of Leo. The entire machinery of drawing icons in the vnode class has been eliminated. The v.computeIcon method tells what the icon should be. The v.iconVal ivar that tells what the present icon is. The event handler simply compares these two values and sets redraw_flag if they don't match.

Handling events. Besides redrawing the screen, Leo must handle events or commands that change the text in the outline or body panes. It is surprisingly difficult to ensure that headline and body text corresponds to the vnode and tnode corresponding to presently selected outline, and vice versa. For example, when the user selects a new headline in the outline pane, we must ensure that 1) the vnode and tnode of the previously selected node have up-to-date information and 2) the body pane is loaded from the correct data in the corresponding tnode. Early versions of Leo attempted to satisfy these conditions when the user switched outline nodes. Such attempts never worked well; there were too many special cases. Later versions of Leo, including leo.py, use a much more direct approach. The event handlers make sure that the vnode and tnode corresponding to the presently selected node are always kept up-to-date. In particular, every keystroke in the body pane causes the presently selected tnode to be updated immediately. There is no longer any need for the c.synchVnode method, though that method still exists for compatibility with old scripts.

The leoTree class contains all the event handlers for the body and outline panes. The actual work is done in the idle_head_key and idle_body_key methods. These routines are surprisingly complex; they must handle all the tasks mentioned above, as well as others. The idle_head_key and idle_body_key methods should not be called outside the leoTree class. However, it often happens that code that handles user commands must simulate an event. That is, the code needs to indicate that headline or body text has changed so that the screen may be redrawn properly. The leoTree class defines the following simplified event handlers: onBodyChanged, onBodyWillChange, onBodyKey, onHeadChanged and onHeadlineKey. Commanders and subcommanders call these event handlers to indicate that a command has changed, or will change, the headline or body text. Calling event handlers rather than c.beginUpdate and c.endUpdate ensures that the outline pane is redrawn only when needed.
The following is a definition of clones from the user's point of view.

Definition 1

A clone node is a copy of a node that changes when the original changes. Changes to the children, grandchildren, etc. of a node are simultaneously made to the corresponding nodes contained in all cloned nodes. Clones are marked by a small clone arrow by its leader character.

As we shall see, this definition glosses over a number of complications. Note that all cloned nodes (including the original node) are equivalent. There is no such thing as a "master" node from which all clones are derived. When the penultimate cloned node is deleted, the remaining node becomes an ordinary node again.

Internally, the clone arrow is represented by a clone bit in the status field of the vnode. The Clone Node command sets the clone bits of the original and cloned vnodes when it creates the clone. Setting and clearing clone bits properly when nodes are inserted, deleted or moved, is non-trivial. We need the following machinery to do the job properly.

Two vnodes are joined if a) they share the same tnode (body text) and b) changes to any subtree of either joined vnodes are made to the corresponding nodes in all joined nodes. For example, Definition 1 defines clones as joined nodes that are marked with a clone arrow. Leo links all vnodes joined to each other in a circular list, called the join list. For any vnode n, let J(n) denote the join list of n, that is, the set of all vnodes joined to n. Again, maintaining the join lists in an outline is non-trivial.

The concept of structurally similar nodes provides an effective way of determining when two joined nodes should also have their cloned bit set. Two joined nodes are structurally similar if a) their parents are distinct but joined and b) they are both the nth child of their (distinct) parents. We can define cloned nodes using the concept of structurally similar nodes as follows:

Definition 2

Clones are joined vnodes such that at least two of the vnodes of J(n) are not structurally similar to each other. Non-cloned vnodes are vnodes such that all of the vnodes of J(n) are structurally similar. In particular, n is a non-cloned vnode if J(n) is empty.

Leo ensures that definitions 1 and 2 are consistent. Definition 1 says that changes to the children, grandchildren, etc. of a node are simultaneously made to the corresponding nodes contained in all cloned nodes. Making "corresponding changes" to the non-cloned descendents of all cloned nodes insures that the non-cloned joined nodes will be structurally similar. On the other hand, cloned nodes are never structurally similar. They are created as siblings, so they have the same parent with different "child indices." To see how this works in practice, let's look at some examples.

Example 1

+ root + a' (1) + a' (2)

This example shows the simplest possible clone. A prime (') indicates a cloned node. Node a in position (1) has just been cloned to produce a' in position (2). Clearly, these two cloned nodes are not structurally similar because their parents are not distinct and they occupy different positions relative to their common parent.

Example 2

If we add a node b to either a' node we get the following tree:

+ root + a' + b + a' + b

The b nodes are structurally similar because the a' nodes are joined and each b node is the first child of its parent.

Example 3

If we now clone either b, we will get:

+ root + a' + b' (1) + b' (2) + a' + b' (1) + b' (2)

All b' nodes must be clones because the nodes marked (1) are not structurally similar to the nodes marked (2).

Dependent nodes are nodes created or destroyed when corresponding linked nodes are created or destroyed in another tree. For example, going from example 1 to example 2 above, adding node b to either node a' causes another (dependent) node to be created as the ancestor of the other node a'. Similarly, going from example 2 to example 1, deleting node b from either node a' causes the other (dependent) node b to be deleted from the other node a'. Cloned nodes may also be dependent nodes. In Example 3, all the b' nodes are dependent on any of the other b' nodes.

We can now give simple rules for inserting and deleting dependent vnodes when other vnodes are created, moved or destroyed. For the purposes of this discussion, moving a node is handled exactly like deleting the node then inserting the node; we need not consider moving nodes further. We insert a new node n as the nth child of a parent node p as follows. We insert n, then for every node pi linked to p, we insert a dependent node ni as the nth child of pi. Each ni is linked to n. Clearly, each ni is structurally similar to n. Similarly, it is easy to delete a node n that is the nth child of a parent node p. We delete each dependent node ni that is the nth child of any node pi linked to p. We then delete n. When inserting or deleting any vnode n we must update its join list, J(n). Updating the join list is easy because the join list is circular: the entire list is accessible from any of its members.

Inserting or deleting nodes can cause the clone bits of all joined nodes to change in non-trivial ways. To see the problems that can arise, consider deleting any of the b' nodes from Example 3. We would be left with the tree in Example 2. There are two remaining b nodes, each with the clone bit set. Unless we know that both b nodes are structurally similar, there would be no way to conclude that we should clear the clone bits in each node. In order to update clone links properly we could examine many special cases, but there is an easier way. Because of definition 2, we can define a shouldBeCloned function that checks J(n) to see whether all nodes of J(n) are structurally similar.

Leo's XML file format does not contain join lists. This makes it easy to change a Leo file "by hand." If join lists were a part of the file, as they are in the Mac version of Leo, corrupting a join list would corrupt the entire file. It is easy to recreate the join lists when reading a file using a dedicated field in the tnode. This field is the head of a list of all vnodes that points to the tnode. After reading all nodes, Leo creates this list with one pass through the vnodes. Leo then convert each list to a circular list with one additional pass through the tnodes.
The find and change commands are tricky; there are many details that must be handled properly. This documentation describes the leo.py code. Previous versions of Leo used an inferior scheme. The following principles govern the LeoFind class:

1. Find and Change commands initialize themselves using only the state of the present Leo window. In particular, the Find class must not save internal state information from one invocation to the next. This means that when the user changes the nodes, or selects new text in headline or body text, those changes will affect the next invocation of any Find or Change command. Failure to follow this principle caused all kinds of problems in the Borland and Macintosh codes. There is one exception to this rule: we must remember where interactive wrapped searches start. This principle simplifies the code because most ivars do not persist. However, each command must ensure that the Leo window is left in a state suitable for restarting the incremental (interactive) Find and Change commands. Details of initialization are discussed below.

2. The Find and Change commands must not change the state of the outline or body pane during execution. That would cause severe flashing and slow down the commands a great deal. In particular, c.selectVnode and c.editVnode methods must not be called while looking for matches.

3. When incremental Find or Change commands succeed they must leave the Leo window in the proper state to execute another incremental command. We restore the Leo window as it was on entry whenever an incremental search fails and after any Find All and Change All command. Initialization involves setting the self.c, self.v, self.in_headline, self.wrapping and self.s_text ivars. Setting self.in_headline is tricky; we must be sure to retain the state of the outline pane until initialization is complete. Initializing the Find All and Change All commands is much easier because such initialization does not depend on the state of the Leo window.

Using Tk.Text widgets for both headlines and body text results in a huge simplification of the code. Indeed, the searching code does not know whether it is searching headline or body text. The search code knows only that self.s_text is a Tk.Text widget that contains the text to be searched or changed and the insert and sel Tk attributes of self.search_text indicate the range of text to be searched. Searching headline and body text simultaneously is complicated. The selectNextVnode() method handles the many details involved by setting self.s_text and its insert and sel attributes.
This section describes Leo's explicit Tangle and Untangle commands. Such commands operate only on @root and @unit trees. The previous chapter discusses the implicit Tangle on Write/Untangle on Read processes used to read and write @file trees.

The Tangle command translates the selected @root tree into one or more well-formatted C source files. The outline should contain directives, sections references and section definitions, as described in Chapter 4. The Untangle command is essentially the reverse of the Tangle command. The Tangle command creates a derived file from an @root tree; the Untangle command incorporates changes made to derived files back into the @root tree.

The Tangle command operates in two passes. The first pass discovers the complete definitions of all sections and places these definitions in a symbol table. The first pass also makes a list of root sections. Definitions can appear in any order, so we must scan the entire input file to know whether any particular definition has been completed.

Tangle's second pass creates one file for each @root node. Tangle rescans each section in the list of roots, copying the root text to the output and replacing each section reference by the section's definition. This is a recursive process because any definition may contain other references. We can not allow a section to be defined in terms of itself, either directly or indirectly. We check for such illegally recursive definitions in pass 2 using the section stack class. Tangle indicates where sections begin and end using comment lines called sentinel lines. The this part of the appendix discusses the format of the sentinels output by the Tangle command.

The key design principle of the Tangle command is this: Tangle must output newlines in a context-free manner. That is, Tangle must never output conditional newlines, either directly or indirectly. Without this rule Untangle could not determine whether to skip or copy newlines.

The Tangle command increases the indentation level of a section expansion the minimum necessary to align the section expansion with the surrounding code. In essence, this scheme aligns all section expansions with the line of code in which the reference to the section occurs. In some cases, several nested sections expansions will have the same indentation level. This can occur, for example, when a section reference in an outline occurs at the left margin of the outline.

This scheme is probably better than more obvious schemes that indent more "consistently." Such schemes would produce too much indentation for deeply nested outlines. The present scheme is clear enough and avoids indentation wherever possible, yet indents sections adequately. End sentinel lines make this scheme work by making clear where the expansion of one section ends and the expansion of a containing section resumes.

Tangle increases indentation if the section reference does not start a line. Untangle is aware of this hack and adjusts accordingly. This extra indentation handles several common code idioms, which otherwise would create under-indented code. In short, Tangle produces highly readable, given the necessity of preserving newlines for Untangle.

Untangle is inherently complex. It must do a perfect job of updating the outline, especially whitespace, from expansions of section definitions created by the Tangle command. Such expansions need not be identical because they may have been generated at different levels of indentation. The Untangle command can not assume that all expansions of a section will be identical in the derived file; within the derived file, the programmer may have made incompatible changes to two different expansions of the same section. Untangle must check to see that all expansions of a section are "equivalent". As an added complication, derived files do not contain all the information found in @root trees. @root trees may contain headlines that generate no code at all. Also, an outline may define a section in several ways: with an @c or @code directive or with a section definition line. To be useful, Untangle must handle all these complications flawlessly. The this part of the appendix discusses the various conventions used in the sentinels output by the Tangle command. These conventions allow the Untangle command to recreate whitespace correctly.

Untangle operates in two passes. The first pass finds definitions in the derived file and enters them into the Untangle Symbol Table, or UST. Definitions often include references to other sections, so definitions often include nested definitions of referenced sections. The first pass of Untangle uses a definition stack to keep track of nested definitions. The top of the stack represents the definition following the latest reference, except for the very first entry pushed on the stack, which represents the code in the outline that contains the @root directive. The stack never becomes empty because of the entry for the @root section. All definitions of a section should match--otherwise there is an inconsistent definition. This pass uses a forgiving compare routine that ignores differences that do not affect the meaning of a program.

Untangle's second pass enters definitions from the outline into the Tangle Symbol Table, or TST. The second pass simultaneously updates all sections in the outline whose definition in the TST does not match the definition in the UST. The central coding insight of the Untangle command is that the second pass of Untangle is almost identical to the first pass of Tangle! That is, Tangle and Untangle share key parts of code, namely the skip_body() method and its allies. Just when skip_body() enters a definition into the symbol table, all the information is present that Untangle needs to update that definition.

The following paragraphs discuss code-level details. You should not need to read this section in order to use Leo.

1. Leo uses unicode objects in vnodes and tnodes to denote headline and body text. Note that unicode strings have no encoding; only plain strings have encodings. This means that once an (encoded) plain string has been converted to a unicode string it doesn't matter how the unicode string was created. This is the key that makes Leo's new code robust.

So internally Leo never has to worry about encodings. The only times encoding matter is when encoded strings are converted to and from unicode when Leo reads or writes files (or when the passing strings to and from Tk.Text widgets, but the idea is the same).

2. Python expressions that mix unicode strings u and plain strings s, like:

u + s or u == s or u[5] == s[2:]

are promoted to unicode objects using the "system encoding". This encoding should never be changed, but we can't assume that we know what it is, so for safety we should assume the most restrictive encoding, namely "ascii". With this assumption, Leo's code can't throw an exception during these promotions provided that:

- All strings are converted to unicode when Leo reads files or gets text from Tk.Text widgets. - All string literals in Leo's code have only ascii characters.
Only leo.py supports unlimited undo. Unlimited undo is straightforward; it merely requires that all commands that affect the outline or body text must be undoable. In other words, everything that affects the outline or body text must be remembered.

We may think of all the actions that may be Undone or Redone as a string of beads (undo nodes). Undoing an operation moves backwards to the next bead; redoing an operation moves forwards to the next bead. A bead pointer points to the present bead. The bead pointer points in front of the first bead when Undo is disabled. The bead pointer points at the last bead when Redo is disabled. An undo node is a Python dictionary containing all information needed to undo or redo the operation.

The Undo command uses the present bead to undo the action, then moves the bead pointer backwards. The Redo command uses the bead after the present bead to redo the action, then moves the bead pointer forwards. All undoable operations call setUndoParams() to create a new bead. The list of beads does not branch; all undoable operations (except the Undo and Redo commands themselves) delete any beads following the newly created bead.

I did not invent this model of unlimited undo. I first came across it in the documentation for Apple's Yellow Box classes.
@nocolor @ignore
To convert an @root tree to an @file tree, choose the root of the tree to be converted, then do the following in the Python window:

import c2py c2py.leo1to2()

This script makes numerous changes throughout the tree. It does not, however, change @root to @file, or insert the needed @others directives. You must do that by hand.

To convert @root trees to @file trees by hand:

1. Change the @root node to an @file node. That is, delete the @root <filename> directive from the body text and insert @file <filename> in the headline. Typically, the root node contains a reference like <<methods of class x>> as the last body text. Replace this reference with the @others directive. The expansion of @others is all text that is not part of a section definition.

2. Add @<space> to the start of all doc parts. Leo2 starts syntax coloring in code mode rather than doc mode, so if a doc part starts body text it should start with @<space> or @doc.

3. Replace all section definition lines (like <<name>>=) by @c (or @code). This results in the node being added to the expansion of @others.

4. Remove all unused code from the @file tree. Leo2 does not write derived files whose @file trees contain orphan or @ignored nodes. If an @file tree does contain an orphan or @ignored node, Leo2 issues a warning and writes the entire @file tree to the .leo file instead of the derived file. This prohibition of orphan an @ignored nodes is necessary to make error recovery completely safe and reliable.

5. Make sure that all nodes defining a section have a headline that starts with <<section>>. This will typically be true when converting @root trees that use the @code directive.

6. If a section is referenced in more than one node (a rare occurence in my code), clone the defining node and move one clone under each referencing node.

7. If a node contains the definitions of several sections, place each different definition in a different node.
@ignore
In @root and @unit trees, the @code directive indicates the start of a code section. The headline must contain a valid section name.

In @file trees, the @c directive indicates the start of a code section.

This directive affects only the Weave (Print) command (and possibly the Create CWEB command). It indicates the start of a chapter. This puts the chapter title at the top of each page.
Indicates that syntax coloring is enabled for the body text in which it appears and all descendent nodes ( unless over-ridden in the descendent nodes ).
This directive indicates the delimiters to be used by the Tangle command.
@nocolor

The @delims directive changes the comment strings used to mark sentinel lines. This directive is often used to place Javascript text inside XML or HTML files. This directive is valid only in @file trees. You can use the @comment directive in @unit or @root trees.

The @delims directive contains one or two delimiters, separated by whitespace. If only one delim is present it delimits single-line comments. If two delims are present they delimit block comments. The @delims directive can not be used to change the comment strings at the start of the derived file, that is, the comment strings for the @+leo sentinel and the initial @+body and @+node sentinels. The @delims directive inserts @@delims sentinels into the derived file. The new delimiter strings continue in effect until the next @@delims sentinel in the derived file or the end of the derived file.

Note: Leo can not revert to previous delimiters automatically; you must change back to previous delimiters using another @delims directive. For example:

@color

@delims /* */ Javascript stuff @delims <-- --> HTML stuff

@nocolor Adding, deleting or changing @@delims sentinels will destroy Leo's ability to read the derived file. Mistakes using the @delims directive have no effect on Leo, though such mistakes will thoroughly mess up a derived file as far as compilers, HTML renderers, etc. are concerned.
In @root and @unit trees, the @doc directive indicates the start of a comment enclosed in the beginning and ending block comment delimiters ( see @comment ).

In @file trees, the @(space) (@ followed by a space, tab or newline) indicates the start of an doc part. @(space) is optional: it is not needed if the body text contains no doc part.

Strictly speaking, @file is not a directive; it is not valid in body text.

A headline that starts with @file <filename> forms the root of an @file tree. Leo writes all nodes in the tree to the derived file <filename> when any Save command is executed. The @file tree is recreated from the derived file when the .leo file is opened.

The Leo2 documentation discusses how to write code in @file trees.
The @first directive allows you to place lines at the very start of files derived from @file nodes. For example, the body text of @file spam.py might be:

@first #! /usr/bin/env python

The body text of @file foo.perl might be:

@first #/usr/bin/perl

@first directives are recognized only at the start of the body text of @file nodes. No text may precede @first directives. More than one @first directive may exist, like this:

@first #! /usr/bin/env python @first # more comments.
This directive causes the Tangle and Untangle commands to ignore this node and all its descendents.
The @language directive specifies the comment delimiters and string types used by the Tangle and Untangle.

The form of this directive is

@language x

where x is one of the following: c,c++,html,java,objective-c,pascal,perl,python and shell. Shell files have comments that start with #. Case is ignored in the language specifiers, but not in the @language itself. Thus, the following are equivalent:

@language html @language HTML @language hTmL

but the following is invalid:

@LANGUAGE html

The @comment directive is now officially deprecated: you should use the @language directive whenever possible. At present Untangle will not process an @root or @unit node if an @comment directive is in effect. Why? Because Untangle can't be sure of properly parsing a derived file if the language of the derived file isn't known. It might be possible to assume some defaults in this case, but that is not done at present and is not a high priority.
Indicates that syntax coloring is disabled for the body text in which it appears and all descendent nodes ( unless over-ridden in the descendent nodes ).
The @others directive is valid only in @file trees. The @others directive causes the expansion of all unnamed nodes to be output to the derived file in the position of the @others directive.

For example, a common Python idiom is:

class myClass: << constants of myClass >> @others

The @others directive inserts the expansion of all unnamed sections into the class definition, indented just as the @others directive is indented.

An @file tree may have more than one @others directive. Each @others directive collects only those nodes found in the descendents of the node containing the @others directive.

A single node may contain at most one @others directive. To use more than one @others directive in an @file tree, use the following pattern:

class x: <<members of x>> class y: <<members of y>>

The named nodes <<members of x>> and <<members of y>> contain only an @others directive. Define members of the x and y classes as descendents of the nodes containing @Others.

The @pagewidth directives affects how Tangle outputs block comments: it specifies the width of output pages in columns. The form of the @pagewidth directive is @pagewidth n, where n is a positive integer.
The @path directive specifies the directory to be used if an @file or @root directive does not specify a full path name. The form of the @path directive is @path filename, where filename is taken to be everything following @path to the end of the line.

If the filename in @file pathname or @root pathname is an absolute filename the location of the derived file is specified only by the filename. Otherwise, if the filename is a relative filename, the location of the derived file is relative to:

1. the directory specified the applicable @path directive, or 2. the "Default Tangle Directory" in the Preferences panel if no @path directive is in effect, or 3. the directory in which the .leo resides if the .leo file has ever been saved.

An error occurs if no absolute path can be computed according to these rules, or if the filename or directory does not exist.
This directive starts the text that forms a single derived file, after all section references have been replaced by their definitions.

By default, only descendents of the @root node are examined for section definitions. However, if the @root directive is a descendent of a node containing the @unit directive, all descendents of the @unit node are examined for section definitions.
This directive affects only the Weave (Print) command (and possibly the Create CWEB command). It indicates the start of a section. This puts the section title at the top of each page.
The @silent causes Tangle to output no comments for doc chunks and no sentinel lines for Untangle. Comments written by the user in code sections are always output.

The @tabwidth directives affects how Tangle outputs leading whitespace: it specifies the width of tabs in spaces. The form of the @tabwidth directive is @tabwidth n, where n is a positive integer.
The @terse directive causes Tangle not to output comments for doc chunks. Tangle will output only those sentinel comments needed by Untangle. As always, comments written by the user in code sections are output.

These directives are not valid in @file trees.
Expands the nodes searched for section definitions when processing an @root directive.

By default, only descendents of the @root node are examined for section definitions. However, if the @root directive is a descendent of a node containing the @unit directive, all descendents of the @unit node are examined for section definitions.
The @verbose directive causes Tangle to output comments for doc chunks and sentinel comments needed by Untangle. This is the default behavior if none of the @verbose, @terse or @silent directives are in effect.
The following error messages may be generated by the Tangle commands. Such errors appear in the log window, which will be opened if necessary.
A file error occurred while trying to reopen the temporary file used during tangling.
A file error occurred while trying to change the name of the temporary file used during tangling.
An @code directive appeared in body text whose header does not contain a section name.
An @directive was seen somewhere other than the start of a line.
Tangle detected more than 20 errors.
A section was defined in terms of itself, either directly or indirectly. The trace shows the chain of section definitions that resulted in the illegal definition.

This message is followed by a walkback of the section names that recursively reference the section. The walkback looks like this:

called from << section name >> called from << section name >> ...
Tangle did not write a file because errors were found.
A C-language comment was not properly terminated.
The file name in an @root directive was not terminated with the proper delimiter.
A section name was not properly terminated before the end of the line in which it started.
A C-language string or character constant was not properly terminated.
Something that looks like a section definition was seen in the middle of a line.
A section was defined using more than 100 levels of section definitions (!)

You could easily create an outline containing every computer program ever written in less than 50 levels.
The selected outline contained no @root directive.
A reference to an undefined section was encountered.
Tangle outputs 2-character WEB control code in a comment. This message is given if we find such comments in a code definition.
(The text of the duplicate definition follows.)

The section may have been defined in more than one place.
The indicated section appears in the outline but is never referenced.
The following error messages may be generated by the Untangle commands. Such errors appear in the log window, which will be opened if necessary.
Two expansions of << section name >> were different in the derived file. This typically arises when the programmer changes one of the expansions but not the other.
No end sentinel line was found for a part of the expansion of the code in the @root node. This is likely the result of adding, deleting or altering a sentinel line.
No end sentinel line was found for the expansion of the code in the @root node. This is likely the result of adding, deleting or altering a sentinel line.
The end sentinel name for << section name >> was expected but not found. This is likely the result of adding, deleting or altering a sentinel line.
The end of the file was reached before encountering the end sentinel line for << section name >>. This is likely the result of adding, deleting or altering a sentinel line.
The following are all minor errors, meaning that no data is likely to have been lost. You can proceed as usual.

1. Rereading file. Clone links into this file will be lost

The .leo file is out of synch with the derived file, so non-essential information usually stored in the .leo file will be deleted. Clone links may be severed.

2. Missing file name. Restoring @file tree from .leo file

No file name follows @file

3. Error reading file. Restoring @file tree from .leo file

The file name following @file could not be found

4. File name in @node sentinel does not match file's name

The name of the derived file may have been changed.

5. Ignoring text after @-leo

Some text follows the trailing @-leo sentinel line. Make sure this text is not important.
The following are all serious errors, meaning that the data in a derived file has been corrupted. You should restore the information from a backup .leo file using the Read Outline Only commands, followed by a Write @file Nodes command.

1. Bad @+leo sentinel in <filename> 2. Bad @delims" 3. Bad attribute field in @+node 4. Bad child index in @+node 5. File may have damaged sentinels! 6. Ignoring <sentinel kind> sentinel. Expecting <sentinel kind> 7. Ignoring unexpected @+leo sentinel 8. Missing <sentinel kind> sentinel 9. Missing @file in root @node sentinel 10. Outline corrupted: different nodes have same clone index! 11. Replacing body text of orphan <node name> 12. Unexpected end of file. Expecting <sentinel kind> sentinel 13. Unknown sentinel: <sentinel line>
Errors while writing @file nodes are harmless. No information is lost because all information is written to the .leo file rather than the derived file.

1. Bad @delims directive

2. Can not open <file name>

3. @ignore node: <headline>

The body text of <headline> contains an @ignore directive.

4. Missing file name: <file name>

@file is not followed by a file name.

5. Orphan node: <headline>

The node is referenced by no ancestor node, and no @others directive applies to it.

6. @others already expanded in: <headline>

The node contains more than one @others directive.

7. Rename failed: no file created! (file may be read-only)

Leo's Save commands write derived files to a temporary file, and rename that file to be the derived file only if the old derived file is different from the temporary file. This message means that a problem with the file system prevented the temporary file from being renamed.

8. Path does not exist: <file name>

The path may be specified by the Preferences panel, an @path directive or by a path in the @file node.

9. undefined section: <section name> referenced from: <headline>

The node given by <headline> contains a reference to <<section name>>, but no named node for <<section name>> exists in the descendents of <headline>
New for leo.py v2.0 and Borland Leo 2.6:

Leo suppresses blank lines between sentinels by default. This is an upward compatible change in the format of derived files: leo.py v2.0 can read derived files from all previous versions of Leo, but previous versions of Leo can not read derived files without blanks lines.

To cause leo.py not to remove blank lines do the following. In the section called << initialize atFile ivars >> in leoAtFile.py, change:

self.suppress_newlines = true to self.suppress_newlines = false

Note: leo.py v2.0 can read derived files without blank lines between sentinels regardless of the setting of self.suppress_newlines.
The following sections describe the comment lines embedded in derived files. It is not necessary to understand this in detail. All you need to know is this:

1. Sentinel lines are comment lines whose first character (following the comment delimiter) is @. 2. Do not alter sentinel lines in derived files. Doing so will corrupt the outline structure! 3. Do not alter blank lines in front of, behind or between sentinels. Doing so will corrupt the outline structure in older versions of Leo.
The @delims directive changes the comment strings used to mark sentinel lines. The @delims directive contains one or two delimiters, separated by whitespace. If only one delim is present it delimits single-line comments. If two delims are present they delimit block comments. The @delim directive can _not_ be used to change the comment strings at the start of the derived file, that is, the comment strings for the @+leo sentinel and the initial @+body and @+node sentinels.

NB: Leo can not revert to previous delimiters automatically; you must change back to previous delimiters using another @delims directive. For example:

@delims /* */ Javascript stuff @delims <-- --> HTML stuff
A file produced by an @file node begins with an @+leo sentinel. The last lines of the file should be the matching @-leo sentinel

The comment delimiters that delimit all sentinels are specified by the first line of the file, that is, the @+leo sentinel. This sentinel has the form:

<opening_delim>@leo<closing_delim>

<closing_delim> may be empty, in which case single-line comments are used. The Write code generates single-line comments if possible. Presently, the Write logic generates single line comments for all languages except HTML. (Block comments can be specified using the @comment directive.)
Suppose the parent node of a node N is P and that P has n children. The sentinel

@+node:<child_index>:<status_fields>:<headline>

begins the expansion of node N. The expansion continues until a matching

@-node::<child_index>:<status_fields>:<headline>

sentinel.

The <child_index> field is a number from 1 to n indicating the index of the node in the list of its parent's children.

The <status_field> field the cloneIndex field of the form: "C=nnn", where nnn is an immutable clone index.

The <headline> field contains headline text, not reference text.

The indentation of the expansion is increased by the extra indentation of preceding the reference. tree level never affects indentation level.
If a node contains significant I(non-whitespace) body text, @+body and @-body sentinels surround the text. These sentinels are nested within @node directives.
@+doc and @-doc delimit doc parts within a node that start with @doc. These sentinels are nested within @body directives. Similarly, @+at and @-at delimit doc parts within a node that start with @<whitespace>.

We use the following convention to determine where putDocPart has inserted line breaks: A line in a doc part is followed by an inserted newline if and only if the newline if preceded by whitespace. To make this convention work, the Write deletes the trailing whitespace of all lines that are followed by a "real" newline.
@+others indicates the start of the expansion of an @+others directive, which continues until the matching @-others. @others sentinels are nested within @body sentinels; the expansion of the @others directive always occurs within the body text of some node N.
The @delims directive inserts @@delims sentinels into the derived file. The new delimiter strings continue in effect until the next @@delims sentinel _in the derived file_ or the end of the derived file.

Adding, deleting or changing @@delim _sentinels_ will destroy Leo's ability to read the derived file. Mistakes in using the @delims _directives_ have no effect on Leo, though such mistakes will thoroughly mess up a derived file as far as compilers, HTML renderers, etc. are concerned.
The @@<text> sentinel represents any line starting with @ in body text except @<whitespace>, @doc and @others. Examples:

@@nocolor @@pagewidth 80 @@tabwidth 4 @@code
@verbatim indicates that the next line of the derived file is not a sentinel. This escape convention allows body text to contain lines that would otherwise be considered sentinel lines.

New for leo.py version 2.0 and Borland Leo version 2.6:

@verbatimAfterRef is generated when a comment following a section reference would otherwise be treated as a sentinel. In Python code, an example would be:

<< ref >> #+others

This sentinel is required only when blank lines are suppressed between sentinel lines.
@<<section_name>> represents section reference. If the reference does not end the line, the sentinel line ending the expansion is followed by the remainder of the reference line. This allows the Read code to recreate the reference line exactly.
The following is a list of features I'd like to add to Leo.

Styled text and graphics

Except for syntax coloring, there is no way to style text in an outline. The ultimate would be for Leo to support arbitrary XML code in the body pane. Perhaps someday Tk's text panes will allow this.

Lists of sections

It would be useful to have easily available, possibly as a pop-up menu, a list of the most recently accessed sections, and possibly a full list of all section names as well.
The following documentation applies to @file trees.

@file node: Node whose headline starts with @file filename. Named node: Node whose headline starts with <<section_name>>. Unnamed node: Node that is neither an @file node nor a definition node. Organizer node: An unnamed node with no body text. @ignore node: Node with an @ignore directive in its body text. @others node: Node with an @others directive in its body text. Orphan node: Ordinary node that is not part of any section definition. Directive: A line of body text that starts with '@'. Doc part: Body text from an @<space> or @doc directive to the next @c or @code directive. Code part: Body text from an @c or @code directive to the next @<space> or @directive. Natural tree order: The order of nodes would appear on the screen if all nodes were expanded.

An @file node forms the root of a tree whose expansion forms a derived file.

The expansion of an @file node or a section definition node is the entire text of the node with all section references replaced by their definition. Section references is any text containing <<section name>>. Section references are recognized anywhere within code parts, even within comments or strings.

Definition nodes must be a descendant of the node containing the section reference. If a section is referenced in more than one node, you may use clones to "copy" the definition to more than one location.

Organizer nodes never affect the generated code, except to produce sentinels. In particular, organizer nodes do not affect indentation of code in the derived file.

The body text of each node is assumed to start in code mode. @ or @doc are used to create a doc part. No escape conventions are used: if a line contains << followed by >> then everything between the << and >> becomes a section name.

The @others directive may be used at most once in named nodes and @file nodes. @others refers to descendant nodes that would not otherwise be included in the derived file. More exactly, the expansion of the @others directive in node N is the set of unnamed descendents of N that are the descendents of no named node or @others node other than N.

Often, a single @others appears at the end of the body text of the @file node. The expansion of @others is the concatenation of the body text of all ordinary nodes, in natural tree order.

For a more complex example, suppose we have a tree with the following headlines:

@file name (contains @others in body text) <<section1>> (contains @others in body text) <<section2>> (contains @others in body text) node A1 node A2 node B node C1 node C2

The expansion of @others in node <<section2>> is the concatenation of the expansion of the body texts of nodes A1 and A2. Similarly, the expansion of @others in node <<section1>> is the concatenation of the expansion of the body texts of node B. Finally, the expansion of @others in node @file name is the concatenation of the expansion of nodes C1 and C2.

The derived file corresponding to an @file node is not written if any descendant of the @file node is an orphan. For example, the following outline contains an orphan node:

@file name (no @others) <<section1>> (contains @others) node A node B (orphan node)

Leo2 issues a warning when a .leo file is saved if any @file node contains an orphan or @ignored node.

Typically a single @others directive in the @file node suffices. For example, the body text of the @file node would be something like:

<< preliminary definitions >> @others

Sections may be defined in any descendant of the @file node provided that the method is not a descendant of any definition node.
The following is a list of the major differences between the Macintosh and PC versions of Leo.

1. The PC version does not have a Print (Weave) command. This command is under development.

2. The PC version implements the Untangle command. The commercial Mac version will implement the Untangle command but the free Mac version does not.

3. The PC version uses an XML file format. The commercial Mac version will use the same format. The free Mac version uses an entirely different format.

4. The PC version implements clone windows; the Mac versions don't.

5. Syntax coloring is partially customizable when using the PC version of Leo. However, at present it is not possible to fully save these options.

6. The Preferences panel is functional in the PC version.

The commercial Mac and PC versions of Leo will use the same file format, so nothing special will need to be done to transfer files between these two versions. Transferring information between the commercial PC version and the free Mac version is relatively straightforward.

1. Create a text file in MORE format.

On the Mac, simply copy an outline in Leo and paste into a text file. I use the Metrowerks IDE to create the text file because it handles newlines properly, but almost any text editor will do. Use the text editor to create the text file.

On the PC, use the Export More Text command, then paste the text into any text editor and use the text editor to create the file.

2. Transfer the text file to the other machine.

3. Create an outline using the text file created in step 1.

On the Mac, load the text file into a text editor. Select all the text and copy it to the clipboard. In Leo, choose the Copy Outline command to create the outline.

On the PC, load the text file into a text editor. Select all the text and copy it to the clipboard. In Leo, choose the Import More Text command to create the outline.
Sherlock is a flexible tracing and measurement package for C, C++ and Objective C. All parts of Sherlock are in the public domain.

You will see Sherlock macros throughout Leo's C++ code. The most common of these macros are FTAG, STATB, STATX, TRACEP, TRACEPB and TRACEPX. These Sherlock macros are not functional in the production version of Leo.

Full documentation for Sherlock can be found at: http://personalpages.tds.net/~edream/sl_index.html
Leo now uses unicode internally for most strings. This solves a lot of problems. The children of this node discuss the details...
Leo now uses unicode internally for most strings. The highlights:

1. Leo converts headline and body text to unicode when reading .leo files and derived files. Both .leo files and derived files may specify their encoding, with "utf-8" being used by default. If the encoding used in a derived file is not "utf-8" it is represented in the @+leo sentinel line. For example:

#@+leo-encoding=iso-8859-1.

The "utf-8" encoding is a "lossless" encoding (it can represent all unicode code points), so encoding and decoding to and from utf-8 plain strings will never cause a problem. When reading or writing a character not in a "lossy" encoding, Leo converts such characters to '?' and issues a warning.

2. When writing .leo files and derived files Leo uses the same encoding used to read the file, again with "utf-8" used as a default.

3. leoConfig.py contains two settings used to specify the default encoding used for .leo and derived files:

default_derived_file_encoding = UTF-8 new_leo_file_encoding = UTF-8

I do not recommend changing new_leo_file_encoding (see the comments in leoConfig.leo) and you may set default_derived_file_encoding to anything that makes sense for you.

4. You may use the @encoding directive to specify the encoding used in a derived file. You can't mix encodings in a single derived file.

This is all should need to know to use extended character sets on Leo.
leoConfig.py now contains two new settings, with the defaults shown below:

default_derived_file_encoding = UTF-8 new_leo_file_encoding = UTF-8

These control the default encodings used when writing derived files and .leo files.

I do not recommend changing new_leo_file_encoding (see the comments in leoConfig.leo)

You may set default_derived_file_encoding to anything that makes sense for you.
This Appendix describes Leo's file format. Most users of Leo can skip this appendix. This technical information will be of use only to those wanting to process Leo files with special-purpose filters.

Leo's uses XML for its file format. The following sections describe this format in detail. The last node gives an example, and it will be useful to refer to this example frequently.

In the description below sss denotes a string with the XML escapes for the & < and > characters:

& becomes &amp; < becomes &lt; > becomes &gt;

This translation is performed on any text that could contain the & < or > characters.

For those of you who grok DTD's a preliminary DTD for Leo's XML follows. This DTD is under development and may contain syntax or semantics errors. It may also simply be inaccurate.

The format of Leo2 .leo files is very similar to the format of Leo1 .leo files. The major differences are:

1. Leo1 .leo files have file version 1. Leo2 .leo files have file version 2. This allows Leo1 to issue an important warning if a version 2 file is opened.

2. vnodes in Leo2 .leo files contain a new cloneIndex field, which records the immutable clone indices found in derived files.

3. vnodes in Leo2 .leo files contain a new attribute byte, the "orphan" attribute.

In all other respects Leo1 and Leo2 .leo files are identical. Leo1 can open Leo2 files, but Leo1 ignores all information unique to Leo2. However, Leo1 treats @file nodes as regular outline nodes: Leo1 neither reads derived files when outlines (.leo files) are loaded nor writes derived files when outlines are saved.
Leo files start out with the following line declaring the format to be formatted as a type of XML.

<?xml version="1.0" encoding="UTF-8"?>
The <leo_file> tag opens an element that contains the entire file. The </leo_file> tag at the end of the file ends this element.

The following sections describe the elements that may appear between the <leo_file> tag and the </leo_file> tag. These elements must appear in the order given. However, the clone_windows element is optional.
The file header element specifies information used to parse the file or to allocate data structures needed to read the file. The header starts with <leo_header and ends with /> In between are the following four required attribute fields.

file_format="1" indicates the version number of the file format: 1, 2, 3, etc.

tnodes="2" indicates the number of tnodes that appear in the file.

max_tnode_index="2" indicates the largest tnode index.

clone_windows="0" indicates the number of clone windows specified by the file.

I'll say more about tnodes later.
The globals element specifies information relating to the entire file. This element starts with the following the <globals body_outline_ratio="0.50"> tag and ends with </globals> tag.

The body_outline_ratio attribute specifies the ratio of the height of the body pane to the total height of the Leo window. It initializes the position of the splitter separating the outline pane from the body pane.

In between the <globals...> tag and the </globals tag> are the following two elements. These specify the position of the Leo window and Log window in global coordinates:

<global_window_position top="27" left="27" height="472" width="571"/>

<global_log_window_position top="183" left="446" height="397" width="534"/>
The preferences element specifies the preferences that the user can change using the Preferences command. It starts with the <preferences...> tag and ends with </preferences> tag.

The following three attributes may appear before the > that ends the <preferences...> tag.

allow_rich_text="1" tab_width="4" page_width="132"

At present the "Allow Rich Text" option is non-functional.

Following the <preferences...> tag is the following element.

<syntax_prefs>sss</syntax_prefs>

The sss string encodes the settings of the TSyntaxMemo component used for body text. This string is simply passed to and from TSyntaxMemo and is not used by Leo in any other way. Future versions of TSyntaxMemo will allow more settings to be encoded, and those settings will appear between the <preferences...> and </preferences> tags.
The find_panel_settings element specifies the settings of the Find panel at the time the file was saved. This element starts with the <find_panel_settings...> tag and continues until the </find_panel_settings> tag.

Zero or more of the following attributes may appear before the > that terminates the <find_panel_settings...> tag. Leo writes these attributes in the order shown, but they may appear in any order.

whole_word="1" search_body="1" whole_word="1" ignore_case="1" pattern_match="1" search_headline="1" search_body="1" suboutline_only="1" mark_changes="1" mark_finds="1" reverse="1"

The default for all attributes is "0" (unchecked check box) and an attribute is written only if is "1" (checked check box).

The following elements appear after the <find_panel_settings> tag and before the </find_panel_settings> tag.

<find_string>sss</find_string> <change_string>sss</change_string>

The sss strings indicate the find and change strings in the Find panel. Either sss string may be empty. Both sss strings are encoded with the usual XML escapes.
The vnodes element specifies the list of so-called vnodes of an outline.

The vnodes element starts with <vnodes...> and ends with </vnodes>. In between these tags are one or more v elements.
The v element represents a single vnode and has the following form:

<v...><vh>sss</vh> (zero or more nested v elements) </v>

The vh element specifies the headline text. As usual it starts at the <vh> tag and continues until the </vh> tag. sss is the headline text encoded with the usual XML escapes.

As shown above, a v element may contain nested v elements. Each vnode corresponds to a headline on the screen, and vnodes are written to the file in the order they would appear on the screen if all headlines were fully expanded.

The nesting relationship between vnodes gives the outline structure. v elements for child vnodes are nested within the v elements of parent vnodes. Therefore, the outline level of a v element is the number of unclosed v elements containing it.

Zero or more of the following three attributes may appear before the > that terminates the <v...> tag.

t="T23" vtag="V18" a="xxxx"

The t="Tnnn" attribute specifies the tnode associated with this vnode. (See the section describing the <t> tag for more details.

The vtag="Vnnn" attributes specifies the vtag number for this node. This tag is specified only if a clone window exists for this vnode.

The a="xxxx" attribute specifies vnode attributes. The xxxx denotes one or more upper-case letters whose meanings are as follows:

C The vnode is a clone. E The vnode is expanded so its children are visible. M The vnode is marked. T The vnode is the top visible node. V The vnode is the current vnode.

For example, a="CEM" specifies that the vnode is a clone, is expanded and is marked.
The tnodes element specifies the list of tnodes of an outline.

The tnodes element starts with <tnodes...> and ends with </tnodes>. In between these tags are zero or more t elements.
The t element represents a single tnode. Each tnode represents body text. Because of cloning, a tnode may be shared among several vnodes.

tnodes have the following form:

<t...><tb>sss</tb></t>

The tb element specifies the body text. As usual it starts at the <tb> tag and continues until the </tb> tag. sss is the headline text encoded with the usual XML escapes.

The tx="Tnnn" attribute must appear before the > that terminates the <t...> tag. It specifies the tnode index of the tnode. Vnodes refer to this index in their t="Tnnn" attribute.

The rtf="1" attribute may appear before the > that terminates the <t...>tag. If present it specifies that body text is encoded as Rich Text Format. This attribute is not used at present.
The clone_windows element is optional. If present it specifies the list of cloned windows of an outline.

The clone_windows element starts with <clone_windows> and ends with </clone_windows>. In between these tags are one or more clone_window elements.
The clone_window element specifies a single clone window to be opened when the file opens.

The clone_window element has the following form:

<clone_window vtag="Vnnn"> <global_window_position top="nnn" left="nnnn" height="nnn" width="nnn" > </clone_window>

All attributes and elements shown above are required.

The vtag attribute must match the vtag attribute in some vnode, and associates the clone window with the vnode.

The global_window_position element specifies the size and position of the clone window. The top and left attributes specify the position of the clone window in global coordinates. The height and width attributes specify the size of the clone window.
<!-- This is a comment --> <!-- Version 1 of LEO DTD -->

<!DOCTYPE LeoOutlineDocumentType [

<!-- A LEO file consists of the following parts (clone_windows is optional) --> <!ELEMENT leo_file (header, globals, prefs, find_settings, vnodes, tnodes, clone_windows?) >

<!ELEMENT header EMPTY > <!ATTLIST header file_format CDATA #REQUIRED <!-- An integer version number --> tnodes CDATA #REQUIRED <!-- Unused at present --> max_tnode_index CDATA #REQUIRED <!-- The size of the array used to resolve tnode indices --> clone_windows CDATA 0 > <!-- Number of clone windows -->

<!ELEMENT globals (global_window_position? global_log_window_position?) > <!ATTLIST globals body_outline_ratio CDATA 0.5 > <!ELEMENT global_window_position EMPTY > <!ATTLIST global_window_position top CDATA #REQUIRED left CDATA #REQUIRED height CDATA #REQUIRED width CDATA #REQUIRED > <!ELEMENT global_log_window_position EMPTY > <!ATTLIST global_window_position top CDATA #REQUIRED left CDATA #REQUIRED height CDATA #REQUIRED width CDATA #REQUIRED >

<!ELEMENT preferences ( syntax_options? ) > <!ATTLIST preferences <!-- The following specify defaults for preferences --> allow_rich_text CDATA 0 tab_width CDATA 4 page_width CDATA 132 font_name CDATA "Courier New" font_size CDATA 9 > <!ELEMENT syntax_options (#PCDATA) >

<!ELEMENT find_settings (find_string? change_string?) > <!ATTLIST find_settings <!-- The following specify defaults for the find/change panel --> batch CDATA 0 wrap_around CDATA 0 batch CDATA 0 whole_word CDATA 1 ignore_case CDATA 0 pattern_match CDATA 0 search_head_text CDATA 0 search_body_text CDATA 1 suboutline_only CDATA 0 mark_changes CDATA 0 mark_finds CDATA 0 reverse CDATA 0 > <!ELEMENT find_string (#PCDATA) > <!ELEMENT change_string (#PCDATA) >

<!ELEMENT vnodes (v*) > <!ELEMENT v (vh, v*) > <!-- The crucial change: vnode may contain other vnodes --> <!ATTLIST v vtag ID #IMPLIED <!-- for use by clone_windows section --> t IDREF #IMPLIED <!-- the tnode for this vnode, or none --> a CDATA "" > <!-- V=current, M=marked, C=cloned, E=expanded --> <!ELEMENT vh (#PCDATA) >

<!ELEMENT tnodes (t*)> <!ELEMENT t (#PCDATA) > <!ATTLIST t tx ID rtf CDATA 0 > <!-- 1 if the #PCDATA is rtf format -->

<!ELEMENT clone_windows (clone_window*) > <!ELEMENT clone_window (global_clone_window_position) > <!ATTLIST clone_window vref IDREF #REQUIRED > <!-- the vtag of the vnode that owns this clone window --> <!ELEMENT global_clone_window_position EMPTY > <!ATTLIST global_clone_window_position top CDATA #REQUIRED left CDATA #REQUIRED height CDATA #REQUIRED width CDATA #REQUIRED > ]
The following node contains the text of an actual leo file. After that node is a node containing the outline that created the leo file.
<?xml version="1.0" encoding="UTF-8"?> <leo_file> <leo_header file_format="1" tnodes="4" max_tnode_index="4" clone_windows="0"/> <globals body_outline_ratio="0.499"> <global_window_position top="54" left="54" height="550" width="559"/> <global_log_window_position top="2" left="630" height="397" width="336"/> </globals> <preferences allow_rich_text="0" tab_width="4" page_width="72" tangle_bat="0" untangle_bat="0"> <TSyntaxMemo_options>DBSoft6:OPT#8#67100656GWD#1#0GCL#11#-2147483633WRC#1#0IDS#1#1WRO#3#&gt;|:FON#11#Courier NewFOS#1#9STC#8#16777215STB#7#8388608TBC#0#TBD#1#4EFF#323#18,0,0,16777215,;3,0,16777215,;4,8421504,16777215,;5,0,16777215,;6,255,16777215,;10,16711680,16777215,;11,0,16777215,;12,16711680,16777215,;14,16711680,16777215,;15,255,16777215,;16,16711680,16777215,;17,255,16777215,;18,16711680,16777215,;19,32768,16777215,;30,255,16777215,;32,0,16777215,;33,255,16777215,;36,0,16777215,;GDF#1#0MDF#1#0SDF#1#0BDF#1#0FOC#1#0RMG#1#0LNN#13#MS Sans SerifLNS#1#8LNC#11#-2147483640LNT#1#1LNE#0##END#</TSyntaxMemo_options> </preferences> <find_panel_settings whole_word="1" search_body="1"> <find_string>error</find_string> <change_string></change_string> </find_panel_settings> <vnodes> <v t="T1" a="TV"><vh>Headline 1</vh> <v t="T2" a="E"><vh>Headline 2</vh> <v><vh>Headline 3 ( No body text)</vh></v> </v> </v> <v t="T3" a="C"><vh>Headline 4 ( a clone )</vh> <v t="T4"><vh>Headline 5</vh></v> </v> <v t="T3" a="C"><vh>Headline 4 ( a clone )</vh> <v t="T4"><vh>Headline 5</vh></v> </v> </vnodes> <tnodes> <t tx="T1">Text of headline 1.</t> <t tx="T2">Text of headline 2.</t> <t tx="T3">Text of headline 4.</t> <t tx="T4">Text of headline 5.</t> </tnodes> </leo_file>
Text of headline 1.
Text of headline 2.
Text of headline 4.
Text of headline 5.
@nocolor
First, read the introduction to Leo at: http://personalpages.tds.net/~edream/intro.htm

Then study LeoPy.leo, the source code for leo.py. The best way to learn about Leo is to spend 15 or 20 minutes just browsing through the outline, not worrying about details but just seeing how most nodes are organized and how directives and section references are typically used. Then read Chapter 4 to learn the details.

When studying leo.py, I would suggest paying particular attention to the following:

1. The (Projects) tree shows how to use clones to represent tasks.

2. The @file leoNodes.py tree shows how to define more than one class in a single file using several @others directives. Note that the @others directives must be in separate nodes.

3. Most other files show how to use a single @others directive to define one class.

4. Note in particular that the vast majority of methods are defined in unnamed nodes.
The file LeoDocs.leo contains two "Hello World" programs, using @file trees and @root trees.

The file LeoPy.leo contains a large number of other examples, some small, some large and complex.
@file trees and @root trees represent derived files within an outline. Leo outlines may contain both @root and @file trees, and I recommend using @file trees whenever possible.

Indeed, @file trees are much easier to use than @root trees:

1. @file trees require less markup than @root trees; all sections in @root trees must have names, but @file trees usually contain many unnamed sections. Within @file trees, the @others directive acts like a reference to all unnamed sections.

2. The user must explicitly tangle and untangle @root trees using the Tangle and Untangle commands. Leo tangles and untangles @file trees automatically, tangling all changed @file trees when writing an outline and untangling all @file trees when reading an outline.

However, @root trees are more flexible than @file trees.

1. Within @file trees, sections must be defined in descendents of the node that contains the section reference.

2. Within @root trees a section may be defined anywhere within the @root tree, regardless of where the section is referenced. Moreover, the @unit directive expands the scope of section definitions in @root trees so that a section may be referenced in several @root trees.

3. The meaning of section definitions in @root trees are independent of their position within the tree.
You have two options, depending on whether you want to be able to use sections or not.

Option 1: Use @nosentinelsfile trees.

Files derived from @nosentinelsfile trees contain no sentinels. However, Leo creates derived files just as in @file trees. In particular, Leo expands section references and understands the @others directive.

Option 2: Use @silentfiles trees.

Files derived from @silentfile trees contain no sentinels. Moreover, Leo does not expand section references in @silentfile trees. In other words, Leo creates the derived file simply by writing all body text in outline order.

Leo can't update the outline unless the derived file contains sentinels, so Leo does not update @nosentinelfile trees or @silentfile trees automatically when you change the derived file in an external editor.

For complete details about @nosentinelfile trees and @silentfile trees, see the child of this node.
You have two options, depending on whether you want sentinel lines in your derived file or not.

Option 1: Use @rawfiles trees.

Leo creates files derived from @rawfile trees by writing all the nodes of the tree to the derived file in outline order. The derived file _does_ contain some sentinels, so you _can_ update @rawfile trees from changes made to derived files.

Option 1: Use @silentfiles trees.

Files derived from @silentfile trees contain no sentinels. Leo creates the derived file simply by writing all body text in outline order.

Leo can't update the outline unless the derived file contains sentinels, so Leo does not update @silentfile trees automatically when you change the derived file in an external editor.

For complete details about @rawfile and @silentfile trees, see the child of this node.
Q: I'm writing a Windows Script Component, which is an XML file with a CData section containing javascript. I can get the XML as I want it by setting the language to html, but how can I get the tangling comments inside the CData section to be java-style comments rather than html ones?

A: In @file trees you use the @delims directive to change comment delimiters. For example:

@delims /* */ Javascript stuff @delims <-- --> HTML stuff

NB: Leo2 can not revert to previous delimiters automatically; you must change back to previous delimiters using another @delims directive.

In @root trees you can work around this problem using the @silent directive.
Use the @first directive @file or @nosentinelsfile trees. Use the @silent in @root trees.

The @first directive allows you to place lines at the very start of files derived from @file nodes. For example, the body text of @file spam.py might be:

@first #! /usr/bin/env python

The body text of @file foo.perl might be:

@first #/usr/bin/perl

@first directives are recognized only at the start of the body text of @file nodes. No text may precede @first directives. More than one @first directive may exist, like this:

@first #! /usr/bin/env python @first # more comments.
Leo has very good support for cweb. In @file trees can organize any part of cweb code using _noweb_ sections. You can also use @rawfile, @silentfile or @nosentinelsfile trees to create cweb files.

By default, cweb colors @space, @* and @** sections using the same syntax coloring as for LaTeX. In addition, cweb colors C // and /*..*/ comments using LaTeX coloring by default. You may change these defaults using the color_cweb_doc_parts_with_latex and color_cweb_comments_with_latex options in leoConfig.py.

See the child of this node for full details.
It is a fundamental part of the design of @file trees that everything in the tree be part of the derived file. Indeed, the entire tree is recreated from the derived file when the .leo file is read. Therefore, orphan and @ignore nodes are invalid in @file trees. This can never change.

@root trees are more flexible. Tangle ignores nodes that don't define sections, so you may put anything in those nodes and they won't appear in the derived file.

In spite of this increased flexibility, I still highly recommend using @file trees whenever possible. You can organize your outline like this:

+ myClass ..+ ignored stuff ..+ @file myClass

As usual, + denotes a headline. So you simply create a new node, called myClass, that holds your @file tree and stuff you don't want in the @file tree.
There are many ways to customize Leo. The child of this node is a clone of Chapter 8: Customizing Leo of Leo's Users Guide.
Here's how to modify customizeLeo.py.

Step 1: Set use_customizeLeo_dot_py = 1 in leoConfig.py.

Step 2: Find the branch "Customizing Leo" in leoPy.leo.

Step 3: Change the line "@ignore" to "@ ignore".

Step 4: Put your new code in function called customizeLeo in the "@file customizeLeo.py" tree.

Step 5: Save the "@file customizeLeo.py" node. You can do this using the Save command or the Write @file Nodes command. Leo will warning you that you are indeed changing customizeLeo.py. Say OK to the warning. You should see the following in the log window:

writing: customizeLeo.py saved: CustomizeLeo.leo

Step 6: Quit Leo and rerun Leo. You should see a signon message in Leo's log window. If not, the new code probably generated an exception. Correct any errors and try again.

Step 7: Once you have everything working as you wish, go back and undo Step 3. That is, change @ ignore back to @ignore
Once you can create customizeLeo.py you have two ways of modifying Leo: using hooks and overriding methods. Hooks are explained in the "About hooks" node, a child node of the node called "Documentation and security warnings". You will find many examples of using hooks in the @file customizeLeo.py tree.

You can override any of Leo's methods inside a hook using the funcToMethod function, defined in customizeLeo.py. There are one or two examples of doing that in the @file customizeLeo.py tree. Just search for funcToMethod to find those examples. The general idea is to use hooks to call funcToMethod any time a .leo file is opened or a new Leo outline is created.
1. What's the best way to go about adding a new @language?

In the short term, the instructions are in LeoPy.leo in the Notes:How To:How to add support for a new language section. This section contains clones of all relevant parts of Leo that you will change.

Longer term, I very much want to move to a scheme like JEdit's, which uses language description files. Perhaps Leo could even use these files. However, this won't happen for quite a while...

2. How can the changes be best folded back into Leo?

For now, you can only change Leo's source code and submit the changes for inclusion in Leo's "official" code. Longer term you will be able to create your own language description file.

In general, Leo's clones provide an excellent way of solving any organizational problem. The fundamental principle is this:

Clones create multiple views of data

Here is how I use clones to create new views of Leo's source code. Whenever I have a task to do, say a new feature to implement, or a non-trivial bug to fix, I create a new headline to represent that task. Let's call such a headline a _task headline_. By convention, I enclose the headline in parentheses and put an @ignore in the body text of the headline, but that's just a convention I use.

I clone all nodes that relate to that task and move the clones under the headline. Voila. I now have a new view of Leo's source code, containing all and only those nodes that relate to the task. It is now trivial to find the parts of code relating to the task. Note that I can change the cloned nodes under the headline task in any way, and the clones under the @file tree also change and the @file node itself is marked dirty so that when I save the .leo file all affected @file trees are also saved. The task headline is a perfect place to place notes about the task: why some approaches didn't work, to-do lists, test data, whatever. You could even create @file nodes to create test files if you want.

There are situations where you need to use @root trees. However, those situations are rare. Most of the time you can use clones to great effect. For extended examples, look at the (Projects) section in LeoPy.leo. That section contains a large number of views of Leo itself.
leo.py v2.5 and later does this automatically.

For earlier versions, the workaround is as follows:

1. Copy the node with the Copy Node command. 2. Paste it into to _body_ text with a regular paste command. 3. Copy the body text with a regular copy or cut command. 4. Switch to the other copy of Leo, then do Paste Node.
@nocolor

Here are the steps involved in adding a new language.

- Add a new language constant in leoGlobals.<<define global constants>>

- Add a new Tk.Radiobutton in leoPrefs.<<create the Target Language frame>>

- Add a new entry for the language in self.languageNameDict in leoConfig.py.<<initialize constant ivars>>.

This entry represents the name of the language in leoConfig.txt.

- Modify the utility routines set_delims_from_language and set_language to handle the new language.

- Modify the code in leoFileCommands.getPrefs.<< check for syntax coloring prefs >> to support the new language.

This involves making new entries in lists in each function.

- Modyify leoColor.py so that colorizeAnyLanguage will handle the new language:

a. Add an entry in the list called "languages" defined in leoColor.<<configure language-specific settings>>

b. Add a list containing the keywords of the new language to leoColor.<< define colorizer keywords >>

Note: the name of this list must be x_keywords, where x is the entry in language in step a.

For example, to add tcltk:

languages = (..., "tcltk",...) # list of names of keyword lists. tcltk_keywords = ("after", "append", ...) # The keyword list for the tcl/tk language.

c. Add any language-specifig code to leoColor.colorizeAnyLanguage.

For most languages nothing need be done in this step because colorizeAnyLanguage gets the comment delimiters from set_delims_from_language.

- Make the following tests:

1. Test the syntax coloring for the new language by using the @language directive.

2. Test workings of the Preferences Panel by choosing the language in the panel and by looking at code that is _not_ under control of an @language directive.

3. Test the leoConfig.txt by setting default_target_language to the name of the new language. When you restart Leo, the new language should be selected in the Prefs panel.

4. Remove leoConfig.txt, select the new language in the Prefs panel, and save the .leo file. Open the file with a text editor and check to make sure that the <preferences> tag (near the top) contains an entry like this:

<preferences allow_rich_text="0" defaultTargetLanguage="Python"> </preferences>

but with the name of your new language instead of "Python".

5. Create an @root node and verify that you can Tangle it.

# We put an @color here so it is unambiguous.

@ignore @language python @color
# General constants... true = 1 false = 0 # Better than None body_newline = '\n' body_ignored_newline = '\r' prolog_string = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"

# New in leo.py 3.0 prolog_prefix_string = "<?xml version=\"1.0\" encoding=" prolog_version_string1 = "UTF-8" # for leo.py 2.x prolog_version_string2 = "ISO-8859-1" # for leo.py 3.x prolog_postfix_string = "?>"

# These are set by the @langauge directive. # Used by Tangle, Untangle and syntax coloring. ada_language = 1 c_language = 2 # C, C++ or objective C. cweb_language = 3 # CWEB syntax coloring cobol_language = 4 # literate cobol?? fortran_language = 5 # Comments start with C fortran90_language = 6 # Comments start with ! html_language = 7 java_language = 8 lisp_language = 9 pascal_language = 10 plain_text_language = 11 perl_language = 12 # just ## perlpod_language = 13 # ## and =pod and =cut python_language = 14 shell_language = 15 # shell scripts tcltk_language = 16 unknown_language = 17 # Set when @comment is seen.

# Synonyms for the bits returned by is_special_bits... color_bits = 0x00001 comment_bits = 0x00002 cweb_bits = 0x00004 header_bits = 0x00008 ignore_bits = 0x00010 language_bits = 0x00020 nocolor_bits = 0x00040 noheader_bits = 0x00080 noweb_bits = 0x00100 # 0x00200 #unused page_width_bits=0x00400 path_bits = 0x00800 root_bits = 0x01000 # Also represents < < * > > = silent_bits = 0x02000 tab_width_bits =0x04000 terse_bits = 0x08000 unit_bits = 0x10000 verbose_bits = 0x20000
# Frame and title w,target = create_labeled_frame (outer,caption="Default Target Language") w.pack(padx=2,pady=2,expand=1,fill="x")

# Frames for two columns of radio buttons lt = Tk.Frame(target) rt = Tk.Frame(target) lt.pack(side="left") rt.pack(side="right")

# Left column of radio buttons. left_data = [ ("C/C++",c_language), ("CWEB", cweb_language), ("HTML", html_language), ("Java", java_language), ("Pascal", pascal_language) ]

for text,value in left_data: button = Tk.Radiobutton(lt,anchor="w",text=text, variable=self.lang_var,value=value,command=self.set_lang) button.pack(fill="x")

# Right column of radio buttons. right_data = [ ("Perl", perl_language), ("Perl+POD", perlpod_language), ("Plain Text", plain_text_language), ("Python", python_language), ("tcl/tk", tcltk_language) ]

for text,value in right_data: button = Tk.Radiobutton(rt,anchor="w",text=text, variable=self.lang_var,value=value,command=self.set_lang) button.pack(fill="x")
leoKeywords = ( # Leo 2 directives. "@","@c","@code","@doc","@color","@comment", "@delims","@first","@language","@nocolor","@others", "@pagewidth","@path","@tabwidth", # Leo 1 directives. "@ignore","@root","@unit","@silent","@terse","@verbose")

c_keywords = ( # C keywords "auto","break","case","char","continue", "default","do","double","else","enum","extern", "float","for","goto","if","int","long","register","return", "short","signed","sizeof","static","struct","switch", "typedef","union","unsigned","void","volatile","while" # C++ keywords "asm","bool","catch","class","const_cast", "delete","dynamic_cast","explicit","false","friend", "inline","mutable","namespace","new","operator", "private","protected","public","reinterpret_cast","static_cast", "template","this","throw","true","try", "typeid","typename","using","virtual","wchar_t")

cweb_keywords = c_keywords

html_keywords = ( # HTML constructs. "<","</",">", '"', "<!---","<!--","<!", "<%","%>", "<a","</a", "<img", "<cf","</cf", # Common tags: tables "<table","</table", "<td","</td", "<th","</th", "<tr","</tr", "<caption","</caption", "<col","</col", "<colgroup","</colgroup", "<tbody","</tbody", "<tfoot","</tfoot", "<thead","</thead", # Common tags: styles "<style","</style", # Common tags: scripts "<script","</script", # Escapes "&amp;", "&lt;", "&gt;", "&quot;" )

java_keywords = ( "abstract","boolean","break","byte","byvalue", "case","cast","catch","char","class","const","continue", "default","do","double","else","extends", "false","final","finally","float","for","future", "generic","goto","if","implements","import","inner", "instanceof","int","interface","long","native", "new","null","operator","outer", "package","private","protected","public","rest","return", "short","static","super","switch","synchronized", "this","throw","transient","true","try", "var","void","volatile","while")

pascal_keywords = ( "and","array","as","begin", "case","const","class","constructor","cdecl" "div","do","downto","destructor","dispid","dynamic", "else","end","except","external", "false","file","for","forward","function","finally", "goto","if","in","is","label","library", "mod","message","nil","not","nodefault""of","or","on", "procedure","program","packed","pascal" "private","protected","public","published", "record","repeat","raise","read","register", "set","string","shl","shr","stdcall", "then","to","true","type","try","until","unit","uses" "var","virtual","while","with","xor" # object pascal "asm","absolute","abstract","assembler","at","automated", "finalization", "implementation","inherited","initialization","inline","interface", "object","override","resident","resourcestring", "threadvar", # limited contexts "exports","property","default","write","stored","index","name" )

perl_keywords = ( "continue","do","else","elsif","format","for","format","for","foreach", "if","local","package","sub","tr","unless","until","while","y", # Comparison operators "cmp","eq","ge","gt","le","lt","ne", # Matching ooperators "m","s" # Unary functions "alarm","caller","chdir","cos","chroot","exit","eval","exp", "getpgrp","getprotobyname","gethostbyname","getnetbyname","gmtime", "hex","int","length","localtime","log","ord","oct", "require","reset","rand","rmdir","readlink", "scalar","sin","sleep","sqrt","srand","umask", # Transfer ops "next","last","redo","go","dump", # File operations... "select","open", # FL ops "binmode","close","closedir","eof", "fileno","getc","getpeername","getsockname","lstat", "readdir","rewinddir","stat","tell","telldir","write", # FL2 ops "bind","connect","flock","listen","opendir", "seekdir","shutdown","truncate", # FL32 ops "accept","pipe", # FL3 ops "fcntl","getsockopt","ioctl","read", "seek","send","sysread","syswrite", # FL4 & FL5 ops "recv","setsocket","socket","socketpair", # Array operations "pop","shift","split","delete", # FLIST ops "sprintf","grep","join","pack", # LVAL ops "chop","defined","study","undef", # f0 ops "endhostent","endnetent","endservent","endprotoent", "endpwent","endgrent","fork", "getgrent","gethostent","getlogin","getnetent","getppid", "getprotoent","getpwent","getservent", "setgrent","setpwent","time","times","wait","wantarray", # f1 ops "getgrgid","getgrnam","getprotobynumber","getpwnam","getpwuid", "sethostent","setnetent","setprotoent","setservent", # f2 ops "atan2","crypt", "gethostbyaddr","getnetbyaddr","getpriority","getservbyname","getservbyport", "index","link","mkdir","msgget","rename", "semop","setpgrp","symlink","unpack","waitpid", # f2 or 3 ops "index","rindex","substr", # f3 ops "msgctl","msgsnd","semget","setpriority","shmctl","shmget","vec", # f4 & f5 ops "semctl","shmread","shmwrite","msgrcv", # Assoc ops "dbmclose","each","keys","values", # List ops "chmod","chown","die","exec","kill", "print","printf","return","reverse", "sort","system","syscall","unlink","utime","warn")

perlpod_keywords = perl_keywords

python_keywords = ( "and", "del", "for", "is", "raise", "assert", "elif", "from", "lambda", "return", "break", "else", "global", "not", "try", "class", "except", "if", "or", "yield", "continue", "exec", "import", "pass", "while", "def", "finally", "in", "print")

tcltk_keywords = ( # Only the tcl keywords are here. "after", "append", "array", "bgerror", "binary", "break", "catch", "cd", "clock", "close", "concat", "continue", "dde", "encoding", "eof", "eval", "exec", "exit", "expr", "fblocked", "fconfigure","fcopy", "file", "fileevent", "filename", "flush", "for", "foreach", "format", "gets", "glob", "global", "history", "if", "incr", "info", "interp", "join", "lappend", "lindex", "linsert", "list", "llength", "load", "lrange", "lreplace", "lsearch", "lsort", "memory", "msgcat", "namespace", "open", "package", "parray", "pid", "proc", "puts", "pwd", "read", "regexp", "registry", "regsub", "rename", "resource", "return", "scan", "seek", "set", "socket", "source", "split", "string", "subst", "switch", "tell", "time", "trace", "unknown", "unset", "update", "uplevel", "upvar", "variable", "vwait", "while" )
# Define has_string, keywords, single_comment_start, block_comment_start, block_comment_end

(single_comment_start, block_comment_start, block_comment_end) = set_delims_from_language(language)

has_string = language != plain_text_language

languages = ["c","cweb","html","java","pascal","perl","perlpod","python","tcltk"]

keywords = [] if language==cweb_language: for i in c_keywords: keywords.append(i) for i in cweb_keywords: keywords.append(i) else: for name in languages: exec("if language==%s_language: keywords=%s_keywords" % (name,name))

if 1: # 7/8/02: Color plain text unless we are under the control of @nocolor. state = choose(flag,normalState,nocolorState) else: # Stupid: no coloring at all in plain text. state = choose(language==plain_text_language,nocolorState,normalState)

lb = choose(language==cweb_language,"@<","<<") rb = choose(language==cweb_language,"@>",">>")
# Must match longer tags before short prefixes. language = c_language # default

if self.matchTag("CWEB"): language = cweb_language ; self.getDquote(); elif self.matchTag("C"): language = c_language ; self.getDquote() elif self.matchTag("HTML"): language = html_language ; self.getDquote() elif self.matchTag("Java"): language = java_language ; self.getDquote() elif self.matchTag("Pascal"): language = pascal_language ; self.getDquote() elif self.matchTag("PerlPod"): language = perlpod_language ; self.getDquote() elif self.matchTag("Perl"): language = perl_language ; self.getDquote() elif self.matchTag("Plain"): language = plain_text_language ; self.getDquote() elif self.matchTag("Python"): language = python_language ; self.getDquote() elif self.matchTag("tcl/tk"): language = tcltk_language ; self.getDquote()

# print(`language`)

c.target_language = language
# Language names. self.languageNameDict = { c_language: "C", cweb_language: "CWEB", html_language: "HTML", java_language: "Java", pascal_language: "Pascal", perl_language: "Perl", perlpod_language: "PerlPod", plain_text_language: "Plain", python_language: "Python", tcltk_language: "tcl/tk" }

# Names of sections. self.configSection = "config options" self.compareSection = "compare options" self.findSection = "find/change options" self.prefsSection = "prefs panel options" self.recentFilesSection = "recent files" self.colorsSection = "syntax coloring options" self.windowSection = "window options"
# Returns a tuple (single,start,end) of comment delims

def set_delims_from_language(language):

# trace(`language`)

for lang, val in [ (cweb_language, "// /* */"), (c_language, "// /* */"), (java_language, "// /* */"), (fortran_language, "C"), (fortran90_language, "!"), (html_language, "<!-- -->"), (pascal_language, "// { }"), (perl_language, "#"), (perlpod_language, "# =pod =cut"), (plain_text_language, "#"), # 7/8/02: we have to pick something. (shell_language, "#"), (python_language, "#"), (tcltk_language, "#") ]: # 7/18/02 if lang == language: # trace(`val`) return set_delims_from_string(val)

return None, None, None # Indicate that no change should be made
@ Scans the @language directive that appears at s[i]. 'default' returns default_language.

Returns (language, delim1, delim2, delim3) @c

def set_language(s,i,issue_errors_flag,default_language):

tag = "@language" # trace(`get_line(s,i)`) assert(i != None) assert(match_word(s,i,tag)) i += len(tag) ; i = skip_ws(s, i) # Get the argument. j = i i = skip_c_id(s,i) # Allow tcl/tk. arg = string.lower(s[j:i]) if len(arg) > 0: for name, language in [ ("ada", ada_language), ("c", c_language), ("c++", c_language), ("cweb", cweb_language), ("default", default_language), ("fortran", fortran_language), ("fortran90", fortran90_language), ("html", html_language), ("java", java_language), ("lisp", lisp_language), ("objective-c", c_language), ("pascal", pascal_language), ("perl", perl_language), ("perlpod", perlpod_language), ("plain", plain_text_language), # 7/8/02 ("python", python_language), ("shell", shell_language), ("tcl", tcltk_language) ]: # 7/18/02. Note: this also matches tcl/tk.

if arg == name: delim1, delim2, delim3 = set_delims_from_language(language) return language, delim1, delim2, delim3

if issue_errors_flag: es("ignoring: " + get_line(s,i))

return None, None, None, None,
tags = ( "blank", "comment", "cwebName", "docPart", "keyword", "leoKeyword", "link", "name", "nameBrackets", "pp", "string", "tab")

def colorizeAnyLanguage(self,v,body,language,flag):

#trace(`language`)

hyperCount = 0 # Number of hypertext tags self.body = body # For callbacks s = body.get("1.0", "end") sel = body.index("insert") # get the location of the insert point start, end = string.split(sel,'.') start = int(start) # trace(`self.count` + `v`) # trace(`body.tag_names()`)

if 0: # Remove all tags from the selected line. for tag in self.tags: body.tag_remove(tag, index(start,0), index(start,"end")) else: # Remove all tags from body. body.tag_delete( "blank", "comment", "cwebName", "docPart", "keyword", "leoKeyword", "link", "name", "nameBrackets", "pp", "string", "tab") << configure tags >> << configure language-specific settings >> self.count += 1

lines = string.split(s,'\n') n = 0 # The line number for indices, as in n.i for s in lines: n += 1 ; i = 0 ; sLen = len(s) # trace(`n` + ", " + `s`) while i < sLen: progress = i ch = s[i] if state == string3State: << continue python triple string >> continue elif state == docState: << continue doc part >> continue elif state == nocolorState: << continue nocolor state >> continue elif state == blockCommentState: << continue block comment >> continue else: assert(state == normalState)

if has_string and ch == '"' or ch == "'": << handle string >> elif single_comment_start and match(s,i,single_comment_start): << handle single-line comment >> elif block_comment_start and match(s,i,block_comment_start): << start block comment >> elif ch == '#' and language in [c_language,cweb_language]: << handle C preprocessor line >> elif match(s,i,lb) or (language==cweb_language and match(s,i,"@(")): << handle possible section ref or def >> elif ch == '@': << handle possible @keyword >> elif ch in string.letters: << handle possible keyword >> elif ch == ' ': << handle blank >> elif ch == '\t': << handle tab >> else: << handle normal character >> assert(progress < i)
config = app().config assert(config)

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

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

# underline=var doesn't seem to work. if use_hyperlinks: body.tag_config("link",underline=1) # defined body.tag_config("name",underline=0) # undefined else: body.tag_config("link",underline=0) if underline_undefined: body.tag_config("name",underline=1) else: body.tag_config("name",underline=0)

if self.showInvisibles: if 1: # Very poor, and vaguely usable. body.tag_config("blank",background="black",bgstipple="gray25") body.tag_config("tab",background="black",bgstipple="gray50") else: # Doesn't work, but does increase the spacing ;-) body.tag_config("blank",font="Symbol") body.tag_config("tab",font="Symbol") else: body.tag_config("blank",background="white") body.tag_config("tab",background="white")
if language == cweb_language: << handle cweb doc part >> else: << handle noweb doc part >>
word = self.getCwebWord(s,i) if word and len(word) > 0: j = i + len(word) if word in ("@<","@(","@c","@d","@f","@p"): state = normalState # end the doc part and rescan else: # The control code does not end the doc part. body.tag_add("keyword", index(n,i), index(n,j)) i = j if word in ("@^","@.","@:","@="): # Ended by "@>" j = string.find(s,"@>",i) if j > -1: body.tag_add("cwebName", index(n,i), index(n,j)) body.tag_add("nameBrackets", index(n,j), index(n,j+2)) i = j + 2 else: # Everthing up to the next "@" is in the doc part. j = string.find(s,"@",i+1) if j == -1: j = len(s) body.tag_add("docPart", index(n,i), index(n,j)) i = j
if i == 0 and match(s,i,lb): # Possible section definition line. state = normalState # rescan the line. continue if i == 0 and ch == '@': j = self.skip_id(s,i+1) word = s[i:j] word = string.lower(word) else: word = ""

if word in ["@c","@code","@unit","@root","@color","@nocolor"]: # End of the doc part. body.tag_remove("docPart", index(n,i), index(n,j)) body.tag_add("leoKeyword", index(n,i), index(n,j)) i = j ; state = normalState else: # The entire line is in the doc part. body.tag_add("docPart", index(n,i), index(n,sLen)) i = sLen # skipt the rest of the line.
delim = self.delim if delim=="'''": j = string.find(s,"'''",i) elif delim=='"""': j = string.find(s,'"""', i) else: state=normalState ; self.delim = None ; continue

if j == -1: # The entire line is part of the triple-quoted string. body.tag_add("string", index(n,i), index(n,"end")) i = sLen # skipt the rest of the line. else: # End the string body.tag_add("string", index(n,i), index(n,j+3)) i = j + 3 ; state = normalState ; self.delim = None
j = string.find(s,block_comment_end,i) if j == -1: # The entire line is part of the block comment. body.tag_add("comment", index(n,i), index(n,"end")) i = sLen # skipt the rest of the line. else: # End the block comment. k = len(block_comment_end) body.tag_add("comment", index(n,i), index(n,j+k)) i = j + k ; state = normalState
if i == 0 and ch == '@': j = self.skip_id(s,i+1) word = s[i:j] word = string.lower(word) else: word = ""

if word == "@color" and language != plain_text_language: # End of the nocolor part. body.tag_add("leoKeyword", index(n,0), index(n,j)) i = j ; state = normalState else: # The entire line is in the nocolor part. # Add tags for blanks and tabs to make "Show Invisibles" work. for ch in s[i:]: if ch == ' ': body.tag_add("blank", index(n,i)) elif ch == '\t': body.tag_add("tab", index(n,i)) i += 1
if language == python_language: j, state = self.skip_python_string(s,i) body.tag_add("string", index(n,i), index(n,j)) i = j else: j = self.skip_string(s,i) body.tag_add("string", index(n,i), index(n,j)) i = j
k = len(block_comment_start) body.tag_add("comment", index(n,i), index(n,i+k)) i += k ; state = blockCommentState
body.tag_add("comment", index(n,i), index(n,"end")) i = sLen
body.tag_add("pp", index(n,i), index(n,"end")) i = sLen
body.tag_add("nameBrackets", index(n,i), index(n,i+2))

# See if the line contains the rb j = string.find(s,rb+"=",i+2) ; k = 3 if j == -1: j = string.find(s,rb,i+2) ; k = 2 if j == -1: i += 2 else: if language != cweb_language: searchName = body.get(index(n,i), index(n,j+k)) # includes brackets ref = findReference(searchName,v)

if language == cweb_language: body.tag_add("cwebName", index(n,i+2), index(n,j)) elif ref: body.tag_add("link", index(n,i+2), index(n,j)) if self.use_hyperlinks: << set the hyperlink >> elif k == 3: # a section definition body.tag_add("link", index(n,i+2), index(n,j)) else: body.tag_add("name", index(n,i+2), index(n,j)) body.tag_add("nameBrackets", index(n,j), index(n,j+k)) i = j + k
# Set the bindings to vnode callbacks. # Create the tag. # Create the tag name. tagName = "hyper" + `hyperCount` hyperCount += 1 body.tag_delete(tagName) body.tag_add(tagName, index(n,i+2), index(n,j)) ref.tagName = tagName body.tag_bind(tagName,"<Control-1>",ref.OnHyperLinkControlClick) body.tag_bind(tagName,"<Any-Enter>",ref.OnHyperLinkEnter) body.tag_bind(tagName,"<Any-Leave>",ref.OnHyperLinkLeave)
word = None if language == cweb_language: << Handle all cweb control codes >> if not word: << Handle non-cweb @keywords >>
word = self.getCwebWord(s,i) if word: # Color and skip the word. j = i + len(word) body.tag_add("keyword",index(n,i),index(n,j)) i = j

if word in ("@ ","@\t","@\n","@*","@**"): state = docState continue ;

if word in ("@^","@.","@:","@="): # Ended by "@>" j = string.find(s,"@>",i) if j > -1: body.tag_add("cwebName", index(n,i), index(n,j)) body.tag_add("nameBrackets", index(n,j), index(n,j+2)) i = j + 2
j = self.skip_id(s,i+1) word = s[i:j] word = string.lower(word) if i != 0 and word != "@others": 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 language != plain_text_language and (word == "@" or word == "@doc"): # at-space starts doc part body.tag_add("leoKeyword", index(n,i), index(n,j)) # Everything on the line is in the doc part. body.tag_add("docPart", index(n,j), index(n,sLen)) i = sLen ; state = docState elif word == "@nocolor": # Nothing on the line is colored. body.tag_add("leoKeyword", index(n,i), index(n,j)) i = j ; state = nocolorState elif word in leoKeywords: body.tag_add("leoKeyword", index(n,i), index(n,j)) i = j else: i = j
j = self.skip_id(s,i) word = s[i:j] if word in keywords: body.tag_add("keyword", index(n,i), index(n,j)) i = j
body.tag_add("blank", index(n,i)) ; i += 1
body.tag_add("tab", index(n,i)) ; i += 1
# body.tag_add("normal", index(n,i)) i += 1
def getPrefs (self):

c = self.commands ; config = app().config

self.getTag("<preferences") while 1: if self.matchTag("allow_rich_text="): self.getDquote() ; self.getBool() ; self.getDquote() #ignored elif self.matchTag("tab_width="): self.getDquote() ; c.tab_width = self.getLong() ; self.getDquote() elif self.matchTag("page_width="): self.getDquote() ; c.page_width = self.getLong() ; self.getDquote() elif self.matchTag("tangle_bat="): self.getDquote() ; c.tangle_batch_flag = self.getBool() ; self.getDquote() elif self.matchTag("untangle_bat="): self.getDquote() ; c.untangle_batch_flag = self.getBool() ; self.getDquote() # New in version 0.10 elif self.matchTag("output_doc_chunks="): self.getDquote() ; c.output_doc_flag = self.getBool() ; self.getDquote() elif self.matchTag("noweb_flag="): # New in version 0.19: Ignore this flag. self.getDquote() ; self.getBool() ; self.getDquote() elif self.matchTag("extended_noweb_flag="): # New in version 0.19: Ignore this flag. self.getDquote() ; self.getBool() ; self.getDquote() elif self.matchTag("defaultTargetLanguage="): # New in version 0.15 self.getDquote() << check for syntax coloring prefs >> elif self.matchTag("use_header_flag="): self.getDquote() ; c.use_header_flag = self.getBool() ; self.getDquote() else: break self.getTag(">") while 1: if self.matchTag("<defaultDirectory>"): # New in version 0.16. c.tangle_directory = self.getEscapedString() self.getTag("</defaultDirectory>") if not os.path.exists(c.tangle_directory): es("default tangle directory not found:" + c.tangle_directory) elif self.matchTag("<TSyntaxMemo_options>"): self.getEscapedString() # ignored self.getTag("</TSyntaxMemo_options>") else: break self.getTag("</preferences>")

# Override .leo file's preferences if settings are in leoConfig.txt. if config.configsExist: config.setCommandsIvars(c)
@color @language tcl/tk @root "c:\prog\test\tcltktest.txt"

after # 'after' is a keyword all # 'all' is not a keyword

<< stuff >>

# A comment // Not a comment "A string" 'A string'
@ This is a tcl/tk doc part

This is code
Question:

It would be nice if Leo could open empty files. I tend to be "document oriented" rather than "application oriented" in my thinking and prefer "create empty file at location -> open it with program" to "start program -> create new file -> save it at location".

Answer by Paul Paterson

If you are on Windows 98/2000/XP then the procedure is as follows...

1. Start Leo 2. Click New 3. Click Save as... 4. Save the file as "c:\windows\shellnew\leofile.leo" (or c:\winnt for 2000/XP) 5. Open regedit "start...run...regedit" 6. Open HKEY_CLASSES_ROOT and find the ".leo" extension type 7. Go New ... Key from the context menu 8. Call the new key ShellNew 9. Select the new key, right-click, choose New ... String Value from the contextmenu 10. Call it FileName 11. Double-click on the string, and modify it to be the filename of the leofile.leo file you created, including the extension 12. Exit the registry editor and restart Windows Explorer (or you may need to reboot on Windows 98)

Now you should have a New.. Leo File option in Explorer. This creates a duplicate of the file you saved. This can be useful because you could make a template Leo file containing some standard nodes that you always have and then save this.
The import commands insert @ignore directives in the top-level node. This is done so that you won't accidentally overwrite your files after importing them.

I suggest changing the filename following @file or @root, and then removing the @ignore directive. Saving the outline will then create the file.
@nocolor @ignore
We often refer to outline nodes by the directives they contain. For example, an @root node is a node containing an @root directive, an @ignore node is a node containing an @ignore directive, and so on.

Exception: an @file node is a node whose headline starts with @file.
An @root tree is a tree whose is an @root node. An @file tree is a tree whose root is an @file node, and so on.
The pane containing the body text of the currently selected headline in the outline pane.
The text in the body pane. Body text is always associated with a particular headline.
A small blue box in the icon just to the left of a headline that indicates that the node contains body text.
A node directly contained by a node.
Another name for a section or part.
A copy of a tree that changes whenever the original changes. The original and all clones are treated equally: no special status is given to the "original" node.
A small red arrow in the icon just to the left of a headline that indicates that the node is a clone of another node.
A section containing code. Code sections start with @c or @code directives.
A literate programming language invented by Donald Knuth and Silvio Levy. CWEB produces derived files for the C language.

http://www-cs-faculty.stanford.edu/~knuth/cweb.html
To move right all following siblings of a node so that they become children of a node.
The output file created as the result of tangling a node containing an @root directive. The file consists of the expansion of the text following the @root directive.
An offspring of a node. That is, a child, grandchild, etc. of a node.
A keyword, preceded by an '@' sign, that is used to control syntax coloring or the Tangle, Untangle or Weave commands.
A section containing only documentation. That is, a section that start with @. Doc sections are associated with the immediately following code section, if any.
A convention for representing sequences of characters that would otherwise have special meaning. Leo has only one such convention: in @root trees, @@ in the leftmost column of a code part stands for a single @ character.

Note: the markup language Leo uses in body pane is based on noweb. However, Leo does not support noweb's @<< and @>> escape conventions. If a line in a code part contains << followed by >>, then that line does contain a section name, regardless of context. If the << and >> characters should not, if fact, delimit a section name, then you must place the << and >> on separate lines.

Eliminating noweb's escape conventions for << and >> characters solves some difficult problems when automatically untangling derived files. Moreover, it is typically easier to split source lines than to use @<< and @>>. Finally, Leo's Export and Import commands do support the @<< and @>> conventions.
The child of a child of a node.
The organizational unit of an outline. A node. Each headline contains headline text and (possibly empty) body text.
Leo stands for Literate Editor with Outlines. That is, Leo supports both literate programming and outlining.

Leo1 denotes all versions of Leo that write version 1 .leo files, that is, all Windows version of Leo prior to version 2.0. The last version of Leo1, version 1.15, understands enough about Leo2 to issue a warning when opening version 2 files.

Leo2 denotes all versions of Leo that write version 2 .leo files, that is, all versions of leo.py and all Windows versions with version number 2.0 and above. Only Leo2 can generate derived files from @file trees.
A style of programming that aims at producing the highest quality program listings. Literate programming languages apply two fundamental operations to text: weaving and tangling. Leo supports two literate programming languages, CWEB and noweb.

Knuth's book is the best overall introduction to literate programming: Literate Programming, by D. E. Knuth ISBN 0-937073-80-6.

More information is available at Knuth's web site: http://www-cs-faculty.stanford.edu/%7Eknuth/lp.html
Marking a node by placing a red mark near its leader character. Marks can be placed using the Mark command, as the result of Find and Change commands, or as the result of the Mark Changed Roots and Mark Changed Items command. The Tangle Marked command tangles only those subtrees that are marked.
Within an @file tree, a node that contain a section name, like <<x>>, at the start of its headline text.

Within an @file tree, all nodes containing a section reference must have a descendent named node corresponding to the reference.

Within an @root or @unit tree, an @code directive is an abbreviation for <<x>>=, where << x >> is found in the headline.

The organizational unit of an outline. A headline. The combination of headline text and outline text.
A simple literate programming language invented by Norman Ramsey. noweb can produce derived files for any text-based programming language.

http://www.eecs.harvard.edu/~nr/noweb/
The children, grandchildren, etc. of a node.
An unnamed node in an @tree that contains no body text. Such nodes may appear anywhere in an @file tree; they do not affect the derived file in any way. In particular, organizing nodes do not affect indentation in the derived file.
An unnamed node that is either 1) a descendent of a named node or 2) the descendent of no @others node.

Orphan and @ignore nodes are not allowed in @file trees: no derived file is written if an @file node contains an @ignore or orphan node.
1. A visual representation of a hierarchy on the screen.

2. The data contained by the hierarchy.

Outlines are composed of headlines representing data. The combination of a headline and its associated body text is called a node.
he pane containing a visual representation of outlines.
The node the directly contains a node.
Another name for a section or chunk.
A file that can be read by Adobe Acrobat.
To move all children of a node left in an outline so that they become siblings of the node.
The first node of the entire outline. In the PC version of Leo several nodes may be siblings of the root.
The portion of the outline in which a section definition is known. By default the scope of a section definition is the smallest subtree containing both the section and a node containing an @root directive. The @unit directive declares that the subtree containing the @unit directive is the scope for all sections defined in that tree.
A fragment of text that can be incorporated into derived files. A syntactic unit of body text.

There are two kinds of sections: code sections and doc sections. Sections are defined in slightly different ways in @file in @root trees (that's the difference between the two kinds of trees.)

In @file trees -------------- Code sections start with the @c directive. The entire body text of a node is a code section by default, so the @c directive can be omitted if there are no doc sections in the body pane. Doc sections start with @ followed by a space at the start of a line. Doc sections continue until the end of body text or until the next @c or @(space) directive.

In @root trees -------------- Code sections start with the @c directive or section definition lines of the form: << section name >>= Such lines begin named code sections. Other code sections are unnamed. Doc sections start with @ followed by a space at the start of a line. Doc sections continue until the end of body text or until the next @c or @(space) directive.

The term "section" has two related meanings. Sections are syntactic units of text in the body pane. Leo writes the text of section to derived files, so another meaining of "section" is "the text that is written to the derived file." Which meaning is intended should be clear from context.
A name enclosed in << and >>. Section names may contain any characters except newlines and ">>".
A section name appearing in a code part. Tangling replaces all references by their definitions.
Comment lines in files derived from @file nodes. Such lines start with an @ following the opening comment delimiter. Sentinels embed outline structure into derived files. Do not alter such lines! Doing so can corrupt the outline structure.
Nodes with the same parent. Siblings of the root have no parent.
The icon that appears just to the left of headline text in the outline pane. This icon shows: a blue body text box if the node contains body text, a red clone arrow if the node is cloned, and a red marker bar if the node is marked. The icon is outlined in black if the node is dirty, that is, if the node has been changed since the file was last change.
The process of creating derived files from @root or @file trees. Leo tangles @file automatically when writing a .leo file. The user must explicitly tangle @root trees using the Tangle command. Tangling expands all references in an @root node or @file node.
The language used to syntax color text. This language determines the default comment delimiters used during tangling and untangling.
Another name for an outline.
A node whose headline text does not start with << name >>.

Unnamed nodes in @file trees must be a descendent of an @others node and must not be the descendent of any named node.

For example, the following tree is invalid:

@others node << name >> unnamed node

The unnamed node would be an orphan node.
Updating an outline based on changes to derived files. Untangling allows changes to be propagated from derived files back to the outline. Especially useful when fixing syntax errors outside Leo.
The process of creating typeset documentation from a noweb or CWEB source file. Weaving creates documentation. Tangling creates source code files known as derived files.

Leo does not support weaving directly. If you want to weave a file you can create noweb or CWEB files using Leo's export commands, then use the noweb or CWEB systems to weave those files.
@ The following is part of Leo's actual source code. The code is written in C++ and uses Borland's Delphi classes.

For @root trees: The Tangle command will generate derived files from @root trees. The Untangle command will update outlines based on changes made to files derived from @root trees.

For @file trees: The Save command will generate the derived files from @file trees. The Open command will update outlines based on changes made to files derived from @file trees.

When looking through this outline pay particular attention to:

a) How the outline organizes classes, methods and chunks. b) How the @root and @unit directives are used in @root trees. c) How the @file and @others directives are used in @file trees.

If you actually want to create files you must remove the @ignore directive below. @c

@ignore @language c
@ To create the file hello.c in the current directory.

1. Remove the @ignore directive from the parent of this node. 2. Select this headline. 3. Choose the Tangle command from the File menu.

When you look at hello.c you will see that it contains comments starting with ///. Leave these comments alone: they are used by the Untangle command.

@root "sampleHello.c"

<< includes >> << methods >>
@code

#include "stdio.h"
@code

void main ( void ) { printf("Hello World!\n"); }
@ To create the file hello.c in the current directory.

1. Remove the @ignore directive from the parent of this node. 2. Save this file. This will create hello.c automatically.

When you look at hello.c you will see that it contains comments starting with //@. Leave these comments alone! They are needed to recreate the outline when reading this .leo file.
@ This shows how to use Leo to create source files for Leo2. @c

<< includes >> @others
#include "stdio.h"
void main ( void ) { printf("Hello World!\n"); }
@ The vnodes and tnodes classes form the heart of the "model" classes (using the Smalltalk model/view/controller terminology). That is, the vnode and tnode classes represent nearly all the data contained in the outline. These two classes also provide a way of hiding the underlying implementation. For instance, the Delphi classes used to implement outlines (TTreeView, TTreeNode and TTreeNodes) appear only in the implementation of the vnodes and tnodes class.

The developer documentation contains an extended discussion of these two classes. To summarize: a vnode (visual nodes) represents a headlines _at a particular location on the screen_. When a headline is cloned, vnodes must be copied. tnodes, (text nodes) on the other hand, represent body text: a tnode is shared by all vnodes that are clones of each other. In other words, tnodes are the "unit of sharing" of body text.
@unit
@ tnode.h created May 20, 1999.

tnodes represent the body text associated with headlines.

@root "sampleTnode.h"

#ifndef tnode_h_ #define tnode_h_

#include <comctrls.hpp> #include "SynParse.hpp" #include "SyntaxEd.hpp"

class vnode;

class tnode { private: << tnode data members >> public: << tnode constructors & destructors >> << tnode debugging >> << tnode getters >> << tnode setters >> };

#endif // tnode_h_
@code

String * mBodyRTF ; // The string containing the RTF. String * mBodyString ; // The body text itself.

@ At present we use TTReeView to keep track of the headline text. This has advantages and disadvantages.

Pros: It simplifies the "normal" code. It reduces the in-memory storage requirements.

Cons: It forces us to update clone headlines manually. Cloned headlines are duplicated in the output file. @code

#if 0 // Headlines are stored in the TTreeView. String mHeadString ; // The headline string. #endif

@ The following are routine tnode fields. @code

short mStatusBits ; // Dirty bit. (not archived) long mFileIndex ; // File index for file format. vnode * mJoinHead ; // The head of the join list while a file is being read. int mSelectionStart ; // The start of the selected body text. int mSelectionLength ; // The length of the selected body text.

@ New for Leo2: the clone index of this node. This index is immutable so that cross-file clone links will not break.

For @root nodes this field contains the maximum clone index. @code

long mCloneIndex ;
@code

tnode ( long index = 0, char * bodyText = NULL ) ; ~tnode () ;
@code

void edump ( bool verboseFlag = FALSE ) ;
@code

char * bodyCString ( void ) ; long bodyCStringLength ( void ) ; String bodyRTF ( void ) ; String bodyString ( void ) ;

long cloneIndex ( void ) ; long fileIndex ( void ) ; bool hasBody ( void ) ; bool isDirty ( void ) ; // Used by the Tangle command. bool isRichTextBit ( void ) ; bool isVisited ( void ) ; vnode * joinHead ( void ) ; void loadBodyPaneFromTnode ( TSyntaxMemo * syntaxMemo ) ; // Sets syntaxMemo. long refCount ( void ) ; int selectionLength ( void ) ; int selectionStart ( void ) ;
@code

void clearDirty ( void ) ; void clearRichTextBit ( void ) ; void clearVisited ( void ) ; void saveBodyPaneToTnode ( TSyntaxMemo * memo ) ;

void setCloneIndex ( long index ) ; void setDirty ( void ) ; void setFileIndex ( long index ) ; void setJoinHead ( vnode * v ) ; void setRichTextBit ( void ) ; void setSelection ( int start, int length ) ; void setTnodeRTF ( String s ) ; // For reading from files. void setTnodeText ( String s ) ; void setVisited ( void ) ;
@root "sampleTnode.cpp"

#pragma option -w-par // suppress warning: "Parameter is never used"

<< tnode includes >> << tnode abbreviations >> << tnode methods >>
@code

#include <vcl.h> #pragma hdrstop

#include "args.h" #include "tnode.h"

#include "Leo.h"
@code

enum { // Not archived... dirtyBit = 0x01, richTextBit = 0x02, // Determines whether we use <bt> or <btr> tags. visitedBit = 0x04 } ;
<< tnode methods >>=

// Note: all params have defaults, so new tnode() is valid.

tnode::tnode ( long index , char * bodyCString ) { if ( bodyCString ) mBodyString = new String ( bodyCString ) ; else mBodyString = new String ( "" ) ;

mBodyRTF = new String ("") ;

mJoinHead = NULL ; mStatusBits = 0 ; mFileIndex = index ; mSelectionStart = 0 ; // a TSyntaxMemo is not a String. mSelectionLength = 0 ; mCloneIndex = 0 ; }

tnode::~tnode () { delete mBodyString ; delete mBodyRTF ; }
<< tnode methods >>=

void tnode::edump ( bool verboseFlag ) { if ( verboseFlag ) {

} else {

} }
<< tnode methods >>=

String tnode::bodyRTF ( void ) { assert ( mBodyRTF ) ; return *mBodyRTF ; }
<< tnode methods >>=

char * tnode::bodyCString ( void ) { assert ( mBodyString ) ; return mBodyString -> c_str() ; }
<< tnode methods >>=

long tnode::bodyCStringLength ( void ) { assert ( mBodyString ) ;

#if 0 // faster return mBodyString -> Length(); #else // safer return strlen( mBodyString -> c_str() ); #endif }
<< tnode methods >>=

String tnode::bodyString ( void ) { assert ( mBodyString ) ; return *mBodyString ; }
<< tnode methods >>=

void tnode::loadBodyPaneFromTnode ( TSyntaxMemo * syntaxMemo ) { if ( arg_allow_rich_text && this -> isRichTextBit() && mBodyRTF && mBodyRTF -> Length() > 0 ) { TMemoryStream * ms = new TMemoryStream (); ms -> Write ( mBodyRTF -> c_str(), mBodyRTF -> Length() ) ; ms -> Position = 0 ; syntaxMemo -> Lines -> LoadFromStream (ms) ; delete ms ; } else if ( mBodyString && mBodyString -> Length() > 0 ) { syntaxMemo -> Clear(); syntaxMemo -> Text = *mBodyString ; syntaxMemo -> SelStart = mSelectionStart ; syntaxMemo -> SelLength = mSelectionLength ; } else { syntaxMemo -> Clear(); syntaxMemo -> Text = "" ; mSelectionStart = 0 ; mSelectionLength = 0 ; } }
<< tnode methods >>=

int tnode::selectionLength ( void ) { return mSelectionLength ; }
<< tnode methods >>=

int tnode::selectionStart ( void ) { return mSelectionStart ; }
<< tnode methods >>=

bool tnode::isDirty ( void ) { return ( ( mStatusBits & dirtyBit ) != 0 ) ; }
<< tnode methods >>=

bool tnode::isRichTextBit ( void ) { return ( ( mStatusBits & richTextBit ) != 0 ) ; }
<< tnode methods >>=

bool tnode::isVisited ( void ) { return ( ( mStatusBits & visitedBit ) != 0 ) ; }
<< tnode methods >>=

long tnode::cloneIndex ( void ) { return mCloneIndex ; }
<< tnode methods >>=

long tnode::fileIndex ( void ) { return mFileIndex ; }
<< tnode methods >>=

bool tnode::hasBody ( void ) { assert ( mBodyRTF && mBodyString) ; // Be default, these are String("");

if ( arg_allow_rich_text && this -> isRichTextBit() ) return mBodyRTF -> Length() > 0 ; else return mBodyString -> Length() > 0 ; }
<< tnode methods >>=

vnode * tnode::joinHead ( void ) { return mJoinHead ; }
<< tnode methods >>=

void tnode::saveBodyPaneToTnode ( TSyntaxMemo * memo ) { assert(memo);

if ( this -> isRichTextBit() ) { TMemoryStream * ms = new TMemoryStream (); memo -> Lines -> SaveToStream( ms ); // Transfers RTF to stream. char * s = (char *) ( ms -> Memory ) ; delete mBodyRTF ; mBodyRTF = new String ( s , ms -> Size ) ; delete ms ; this -> setSelection ( 0, 0 ) ; } else { delete mBodyString ; mBodyString = new String ( memo -> Text ) ; this -> setSelection ( memo -> SelStart, memo -> SelLength ) ; } }
<< tnode methods >>=

void tnode::setTnodeRTF ( String s ) { if ( this -> isRichTextBit() ) { delete mBodyRTF ; mBodyRTF = new String ( s ) ; } }
@ This sets the text in the tnode from the given string.

<< tnode methods >>=

void tnode::setTnodeText ( String s ) { delete mBodyString ; mBodyString = new String ( s ) ; }
<< tnode methods >>=

void tnode::setSelection ( int start, int length ) { mSelectionStart = start ; mSelectionLength = length ; }
<< tnode methods >>=

void tnode::clearDirty ( void ) { mStatusBits &= ~ dirtyBit ; }
<< tnode methods >>=

void tnode::clearRichTextBit ( void ) { mStatusBits &= ~richTextBit ; }
<< tnode methods >>=

void tnode::clearVisited ( void ) { mStatusBits &= ~visitedBit ; }
<< tnode methods >>=

void tnode::setDirty ( void ) { mStatusBits |= dirtyBit ; }
<< tnode methods >>=

void tnode:: setRichTextBit ( void ) { mStatusBits |= richTextBit ; }
<< tnode methods >>=

void tnode::setVisited ( void ) { mStatusBits |= visitedBit ; }
<< tnode methods >>=

void tnode::setCloneIndex ( long index ) { mCloneIndex = index ; }
<< tnode methods >>=

void tnode::setFileIndex ( long index ) { mFileIndex = index ; }
<< tnode methods >>=

void tnode::setJoinHead ( vnode * v ) { mJoinHead = v ; }
@unit
@ vnodes represent all additional data attached to TTreeNodes.

@root "sampleVnode.h"

#ifndef vnode_h_ #define vnode_h_

#include <vcl.h> #pragma hdrstop

#include <comctrls.hpp>

#undef V_XCLONES

class tnode; class TLeoForm; class Commands; class vnode;

struct dvnode { << dvnode data members >> } ;

class vnode { private: << vnode data members >> << vnode private methods >> public: << vnode access methods >> << vnode comparisons >> << vnode constructors & destructors >> << vnode debugging >> << vnode file methods >> << vnode getters >> << vnode setters >> << vnode tree methods >> };

#endif // vnode_h_
@ The following stores all information needed to restore a vnode that is about to be deleted. Much of this information is held in the TTreeView or TTreeNode classes, so it must be captured before actually deleting any information.

vnode::doDelete does not delete vnodes, nor does it decrease the refCount field in tnodes, so we save the pointers to these nodes here.

@code

dvnode * next ; long level ; // The level of the outline relative to the root. String headline ; // The text of the soon-to-be deleted TTreeNode. vnode * v ; // The vnode. Neither v nor v -> mT will be deleted by doDelete().
@code

vnode * next ( void ) ; vnode * back ( void ) ; vnode * nodeAfterTree ( void ) ; vnode * threadBack ( void ) ; vnode * threadNext ( void ) ; vnode * visBack ( void ) ; vnode * visNext ( void ) ;
@code

String atFileNodeName ( void ) ; bool isAtFileNode ( void ) ; bool isAtIgnoreNode ( void ) ; bool isAtOthersNode ( void ) ; bool matchHeadline ( String pattern ) ;
@code

vnode ( Commands * commands, tnode * t = NULL ) ;
@ The commander for this vnode. This allows us to determine reliably what the current vnode is, regardless of what window is active. It also speeds access to the current body pane, tree view, etc. @code

Commands * mCommands ;

@ The TTreeNode that owns this vnode. The "Data" member of a TTreeNode points to its vnode. Unlike the Yellow Box code, vnodes contain no structure information: that information is retrieved from the corresponding TTreeNode. @code

TTreeNode * mTreeNode;

@ The tnode. tnodes contain only the body text of the vnode: TTreeNodes contain the headline text. @code

tnode * mT;

@ Status bits unique to vnodes. @code

short mStatusBits;

@ The following field links all vnodes that are joined together. Such nodes must be updated together. @code

vnode * mJoinList;

@ The value of the image index associated with the vnode's icon. This allows setIcon() to redraw the icon (and more importantly, refresh the screen), only if the icon actually changes. @code

long mIconVal ;

@ New for Leo2. Cross-file clone information. @code

#ifdef V_XCLONES

String mXCloneName ; // Full path name of the file containing the clone. unsigned long mXCloneIndex ; // Immutable cloneIndex field within the file.

#endif
@code

String description ( void ) ; void edump ( bool verboseFlag = FALSE ) ;
@code

String convertTreeToString ( void ) ; String moreBody ( void ) ; String moreHead ( long firstLevel ) ;
@code

// Getters outside this class. Commands * commands ( void ) ; TLeoForm * leoForm ( void ) ; TTreeNode * treeNode ( void ) ; TTreeNodes * treeNodes ( void ) ; TTreeView * treeView ( void ) ;

char * bodyCString ( void ) ; char * headCString ( void ) ;

long bodyCStringLength ( void ) ; long headCStringLength ( void ) ;

String bodyRTF ( void ) ; String bodyString ( void ) ; String headString ( void ) ;

long childIndex ( void ) ; vnode * currentVnode ( void ) ; vnode * findRoot ( void ) ; vnode * firstChild ( void ) ; bool hasChildren ( void ) ; long iconVal ( void ) ; bool isAncestorOf ( vnode * v ) ; bool isCloned ( void ) ; bool isDirty ( void ) ; bool isDummy ( void ) ; bool isExpanded ( void ) ; // Uses outline class. bool isExpandedBitSet ( void ) ; // Uses status bits. bool isMarked ( void ) ; bool isOrphan ( void ) ; bool isRoot ( TTreeView * treeView ) ; bool isSelectedBitSet ( void ) ; bool isTopBitSet ( void ) ; bool isVisible ( void ) ; bool isVisited ( void ) ; vnode * joinList ( void ) ; vnode * lastChild ( void ) ; vnode * lastNode ( void ) ; long level ( void ) ; vnode * nthChild ( long n ) ; long numberOfChildren ( void ) ; vnode * parent ( void ) ; int selectionStart ( void ) ; int selectionLength ( void ) ; short status ( void ) ; // Returns all the status bits. tnode * t ( void ) ;

#ifdef V_XCLONES String xCloneName ( void ) ; long xCloneIndex ( void ) ; #endif
@ The following must must not be called externally. @code

vnode * cloneNode ( void ) ; void linkNodeAsChildOfAtIndex (vnode * theParent, long index ) ; void unlinkNode ( void ) ;
@code

void appendStringToBody ( String s ) ;

void clearClonedBit ( void ) ; void clearDirty ( void ) ; void clearDirtyJoined ( void ) ; void clearDummy ( void ) ; void clearMarked ( void ) ; void clearOrphan ( void ) ; void clearVisited ( void ) ;

long computeIcon ( void ) ;

void initClonedBit ( bool val ) ; void initDirtyBit ( void ) ; void initExpandedBit( void ) ; void initHeadString ( String s ) ; void initMarkedBit ( void ) ; void initSelectedBit ( void ) ; void initStatus ( short status ) ; void initTopBit ( void ) ;

void setAncestorAtFileNodeDirty ( void ) ; void setBodyStringOrPane ( String s ) ; void setClonedBit ( void ) ; void setDirty ( void ) ; void setDirtyJoined ( void ) ; void setDummy ( void ) ; void setHeadString ( String s ) ; void setHeadStringOrHeadline( String s ) ; void setIcon ( void ) ; void setIconVal ( long val ) ; // sets mIconVal void setIconWithVal ( long val ) ; // uses val to draw icon. void setJoinList ( vnode * v ) ; void setMarked ( void ) ; void setOrphan ( void ) ; void setSelection ( int start, int length ) ; void setT (tnode * t ) ; void setTreeNode ( TTreeNode * treeNode ) ; void setVisited ( void ) ;

#ifdef V_XCLONES void setXCloneName ( String name ) ; void setXCloneIndex ( long index ) ; #endif

void trimTrailingLines ( void ) ;
@code

// Entry points...(These call TTreeNode methods) vnode * clone ( vnode * v ) ; void createDependents ( void ) ; dvnode * doDelete ( vnode * newVnode ) ; // delete is a C++ keyword. void freeDVnodes ( dvnode * dv ) ; vnode * insertAfter ( tnode * t = NULL ) ; vnode * insertAsLastChild ( tnode * t = NULL ) ; vnode * insertAsNthChild ( long index, tnode * t = NULL ) ; void moveAfter ( vnode * back ) ; void moveToNthChildOf ( vnode * parent, long index ) ; void moveToRoot ( void ) ; vnode * restoreOutlineFromDVnodes ( dvnode *dv, vnode *parent, vnode *back ) ;

// Helpers... void clearTreeNodeFields ( void ) ; vnode * cloneTree ( vnode *oldTree ) ; void copyCloneBitsTo ( vnode *tree2 ) ; void copyTree( vnode * oldRoot, vnode * newRoot ) ; void destroyDependents ( void ) ; void destroyTree ( void ) ; void invalidOutline ( String message ) ; bool isJoinedTo ( vnode *v ) ; bool isOnJoinListOf ( vnode *v2 ) ; void joinNodeTo ( vnode *v2 ) ; void joinTreeTo ( vnode *tree2 ) ; dvnode * saveOutlineWithLevel ( long level ) ; bool shouldBeClone ( void ) ; void unjoinNode ( void ) ; void unjoinTree ( void ) ; bool validateOutlineWithParent (vnode *p ) ;
@ vnode.w.cpp created May 19, 1999.

@root "sampleVnode.cpp"

#pragma option -w-par // suppress "Parameter not used" warning

<< vnode includes >> << vnode abbreviations >> << vnode methods >>
@code

#include <vcl.h> #pragma hdrstop

#include "CloneUnit.h" #include "Commands.h" #include "LeoWindow.h" #include "MainUnit.h" #include "tnode.h" #include "vnode.h" #include "Utils.h"

#include "Leo.h"
@ Define the meaning of vnode status bits. @code

enum { // Archived. clonedBit = 0x01 , // TRUE: vnode has clone mark. dummyBit = 0x02 , // TRUE: vnode is a placeholder under @file node. expandedBit = 0x04 , // TRUE: vnode is expanded. markedBit = 0x08 , // TRUE: vnode is marked orphanBit = 0x10 , // TRUE: vnode saved in .leo file, not derived file. selectedBit = 0x20 , // TRUE: vnode was current vnode when saved. topBit = 0x40 , // TRUE: vnode was top vnode when saved.

// Not archived visitedBit = 0x80 } ;
<< vnode methods >>=

vnode::vnode ( Commands * commands, tnode * t ) { assert(t); assert(commands); mCommands = commands ; mTreeNode = NULL ; mT = t ; mStatusBits = 0 ; mJoinList = NULL ; mIconVal = -1 ; }
@ Returns the filename following @file in the receivers's headline, or the empty string if the receiver is not an @file node.

<< vnode methods >>=

String vnode::atFileNodeName ( void ) { String h = this -> headString(); char * ip = h . c_str(); char * limit = ip + strlen(ip); if ( ip + 5 < limit && match_word(ip, limit, "@file") ) { ip += 5 ; String s (ip, limit - ip ) ; return s.Trim(); } else return "" ; }
@ Returns TRUE if the receiver's headline starts with @file.

<< vnode methods >>=

bool vnode::isAtFileNode ( void ) { String s = this -> atFileNodeName(); return s . Length() > 0 ; }
@ Returns TRUE if the receiver contains @ignore in its body at the start of a line.

<< vnode methods >>=

bool vnode::isAtIgnoreNode ( void ) { String body = this -> bodyString(); char *ip = body.c_str(); return is_special (ip, body.Length(), "@ignore"); }
@ Returns TRUE if the receiver contains @others in its body at the start of a line.

<< vnode methods >>=

bool vnode::isAtOthersNode ( void ) { String body = this -> bodyString(); char *ip = body.c_str(); return is_special (ip, body.Length(), "@others"); }
@ Returns TRUE if the headline matches the pattern ignoring whitespace. The headline may contain characters following the successfully matched pattern.

<< vnode methods >>=

bool vnode::matchHeadline ( String pattern ) { String headline = this -> headString(); char * ip = headline . c_str(); char * pat = pattern . c_str(); for (;;) { while ( is_ws(*pat) ) ++pat ; while ( is_ws(*ip) ) ++ip ; if ( *pat == '\0' ) return true ; // Headline need not end. if (tolower(*pat) != tolower(*ip)) return FALSE ; ++pat ; ++ip; } }
<< vnode methods >>=

void vnode::edump ( bool verboseFlag ) { if ( mT ) mT -> edump ( verboseFlag ) ; }
<< vnode methods >>=

String vnode::description ( void ) { return "v: " + this -> headString() ; }
@ Convert the outline to a string in "MORE" format.

<< vnode methods >>=

String vnode::convertTreeToString ( void ) { String s ; String body ; String newline ("\r\n") ; vnode * v = this ; long level1 = v -> level() ; vnode * after = v -> nodeAfterTree() ;

while ( v && v != after ) { s += v -> moreHead ( level1 ) ; s += newline ; body = v -> moreBody (); if ( body . Length() > 0 ) { s += body ; s += newline ; } v = v -> threadNext() ; }

// s += newline ; return s ; }
@ Return the headline string in MORE format.

<< vnode methods >>=

String vnode::moreHead ( long firstLevel ) { long level = this -> level() - firstLevel ; String s ;

// 8/17/00 Protect against bad level arg in script. s += AnsiString::StringOfChar( '\t', max(0,level) ) ; s += ( this -> hasChildren() ? "+ " : "- ") ; s += this -> headString();

return s; }
@ Return the body string in MORE format.

<< vnode methods >>=

String vnode::moreBody ( void ) { String s = this -> bodyString(); long index = 1 ; // Strings are 1-based.

// Insert a backslash before any leading plus, minus or backslash. while ( index <= s . Length() ) { if ( index == 1 || s [ index ] == body_newline ) { if ( s [ index ] == body_newline ) ++ index ; if ( index <= s . Length() ) { char c = s [ index ] ; if ( c == '+' || c == '-' || c == '\\' ) s . Insert( "\\", index ) ; } } ++ index ; }

return s ; }
@ The first child has index 1.

<< vnode methods >>=

long vnode::childIndex ( void) { assert(mTreeNode); return mTreeNode -> Index ; }
<< vnode methods >>=

vnode * vnode::firstChild ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> getFirstChild() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
<< vnode methods >>=

bool vnode::hasChildren ( void ) { assert(mTreeNode); return mTreeNode -> HasChildren ; }
<< vnode methods >>=

vnode * vnode::lastChild ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> GetLastChild() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
<< vnode methods >>=

vnode * vnode::nthChild ( long n ) { vnode * child = this -> firstChild(); long i = 0 ;

while ( child ) { if ( i == n ) return child ; ++ i ; child = child -> next(); } return NULL ; }
<< vnode methods >>=

long vnode::numberOfChildren ( void ) { assert(mTreeNode); return mTreeNode -> Count ; }
<< vnode methods >>=

#ifdef V_XCLONES

String vnode::xCloneName ( void ) { return mXCloneName ; }

#endif
<< vnode methods >>=

#ifdef V_XCLONES

long vnode::xCloneIndex ( void ) { return mXCloneIndex ; }

#endif
@ Return the previous sibling, or NULL.

<< vnode methods >>=

vnode * vnode::back ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> getPrevSibling() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
@ This function returns the last node of the receiver's tree.

<< vnode methods >>=

vnode * vnode::lastNode ( void ) { vnode * v = this ; long level = this -> level() ; vnode * result = NULL ;

while ( v ) { result = v; v = v -> threadNext() ; if ( v == NULL || v -> level() <= level ) break; }

return result; }
@ This function returns the indentation level of the receiver. The root nodes have level 0, their children have level 1, and so on.

<< vnode methods >>=

long vnode::level ( void ) { assert(mTreeNode); return mTreeNode -> Level ; }
@ Return the next sibling, or NULL.

<< vnode methods >>=

vnode * vnode::next (void) { assert(mTreeNode); TTreeNode * n = mTreeNode -> getNextSibling() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
<< vnode methods >>=

vnode * vnode::parent ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> Parent ; return n ? (vnode *) ( n -> Data ) : NULL ; }
@ Return the vnode following the tree whose root is the receiver.

<< vnode methods >>=

vnode * vnode::nodeAfterTree ( void ) { vnode * next = this -> next() ; vnode * p = this -> parent() ;

while ( next == NULL && p != NULL ) { next = p -> next() ; p = p -> parent() ; } return next ; }
@ Return the previous element of the outline, or NULL if at the start of the outline.

<< vnode methods >>=

vnode * vnode::threadBack ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> GetPrev() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
@ Return the next item in the outline, or NULL if at the end of the outline.

<< vnode methods >>=

vnode * vnode::threadNext ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> GetNext() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
<< vnode methods >>=

vnode * vnode::visBack ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> GetPrevVisible() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
<< vnode methods >>=

vnode * vnode::visNext ( void ) { assert(mTreeNode); TTreeNode * n = mTreeNode -> GetNextVisible() ; return n ? (vnode *) ( n -> Data ) : NULL ; }
<< vnode methods >>=

char * vnode::bodyCString ( void ) { assert ( mT ) ; return mT -> bodyCString() ; }
<< vnode methods >>=

long vnode::bodyCStringLength ( void ) { assert ( mT ) ; return mT -> bodyCStringLength() ; }
<< vnode methods >>=

String vnode::bodyRTF ( void ) { assert(mT); return mT -> bodyRTF(); }
<< vnode methods >>=

String vnode::bodyString ( void ) { assert (mT); return mT -> bodyString() ; }
<< vnode methods >>=

char * vnode::headCString ( void ) { assert(mTreeNode); return mTreeNode -> Text . c_str(); }
<< vnode methods >>=

long vnode::headCStringLength ( void ) { assert(mTreeNode); return mTreeNode -> Text . Length() ; }
<< vnode methods >>=

String vnode::headString ( void ) { assert(mTreeNode); return mTreeNode -> Text ; }
<< vnode methods >>=

bool vnode::isCloned ( void ) { return ( ( mStatusBits & clonedBit ) != 0 ) ; }
<< vnode methods >>=

bool vnode::isDirty ( void ) { return mT -> isDirty() ; }
<< vnode methods >>=

bool vnode::isDummy ( void ) { return ( ( mStatusBits & dummyBit ) != 0 ) ; }
<< vnode methods >>=

bool vnode::isExpanded ( void ) { assert(mTreeNode); return mTreeNode -> Expanded ; }
<< vnode methods >>=

bool vnode::isExpandedBitSet ( void ) { return ( ( mStatusBits & expandedBit ) != 0 ) ; }
<< vnode methods >>=

bool vnode::isMarked ( void ) { return ( ( mStatusBits & markedBit ) != 0 ) ; }
<< vnode methods >>=

bool vnode::isOrphan ( void ) { return ( ( mStatusBits & orphanBit ) != 0 ) ; }
<< vnode methods >>=

bool vnode::isSelectedBitSet ( void ) { return ( ( mStatusBits & selectedBit ) != 0 ) ; }
<< vnode methods >>=

bool vnode::isTopBitSet ( void ) { return ( ( mStatusBits & topBit ) != 0 ) ; }
@ Return TRUE if all parents are expanded.

<< vnode methods >>=

bool vnode::isVisible ( void ) { assert(mTreeNode); return mTreeNode -> IsVisible ; }
<< vnode methods >>=

bool vnode::isVisited ( void ) { return ( ( mStatusBits & visitedBit ) != 0 ) ; }
<< vnode methods >>=

short vnode::status ( void ) { return mStatusBits ; }
@ Return the root (the first node) of the tree containing the receiver. This is called by the commands class.

<< vnode methods >>=

vnode * vnode::findRoot ( void ) { vnode * v = this ;

while ( v && v -> parent() ) v = v -> parent() ;

while ( v && v -> back() ) v = v -> back();

return v ; }
@ This method returns the active form.

<< vnode methods >>=

Commands * vnode::commands ( void ) { assert(mCommands); return mCommands;

#if 0 // old code TLeoForm * leoForm = this -> leoForm() ; assert(leoForm); // Vnodes are _always_ contained in a TLeoForm. return leoForm -> commands(); #endif }
@ This method returns the TLeoForm containing v. We can not assume that this form is the active form.

<< vnode methods >>=

TLeoForm * vnode::leoForm ( void ) { assert(mCommands && mCommands -> mLeoForm); return mCommands -> mLeoForm ;

#if 0 // old code assert(this -> mTreeNode); TTreeView * treeView = (TTreeView *) (this -> mTreeNode -> TreeView ); assert(treeView);

// The TTreeView's parent is a TPanel. TWinControl * winControl = treeView -> Parent ; assert ( String(winControl -> ClassName()) == String("TPanel") ); TPanel * panel = dynamic_cast<TPanel *>(treeView -> Parent) ;

// The TPanel's parent is a TLeoForm. TWinControl * winControl2 = panel -> Parent ; assert ( String(winControl2 -> ClassName()) == String("TLeoForm") ); TLeoForm * leoForm = dynamic_cast<TLeoForm *>(panel -> Parent) ; return leoForm ; #endif }
<< vnode methods >>=

TTreeNode * vnode::treeNode ( void ) { assert(mTreeNode); return mTreeNode ; }
<< vnode methods >>=

TTreeNodes * vnode::treeNodes ( void ) { assert(mTreeNode); TTreeView * treeView = (TTreeView *) mTreeNode -> TreeView ; assert(treeView && treeView -> Items); return treeView -> Items ; }
<< vnode methods >>=

TTreeView * vnode::treeView ( void ) { assert(mTreeNode && mTreeNode -> TreeView); return (TTreeView *) mTreeNode -> TreeView ; }
@ We can not use this -> treeView() -> Selected because the Selected property will be NULL if the TLeoForm is not the current form.

<< vnode methods >>=

vnode * vnode::currentVnode ( void ) { assert(mCommands); return mCommands -> currentVnode(); }
<< vnode methods >>=

long vnode::iconVal ( void ) { return mIconVal ; }
<< vnode methods >>=

bool vnode::isAncestorOf ( vnode * v ) { for ( v = v -> parent() ; v ; v = v -> parent() ) if ( v == this ) return TRUE ;

return FALSE ; }
<< vnode methods >>=

bool vnode::isRoot ( TTreeView * treeView ) { return this -> threadBack() == NULL ; }
<< vnode methods >>=

vnode * vnode::joinList ( void ) { return mJoinList ; }
<< vnode methods >>=

int vnode::selectionStart ( void ) { return mT -> selectionStart(); }
<< vnode methods >>=

int vnode::selectionLength ( void ) { return mT -> selectionLength(); }
<< vnode methods >>=

tnode * vnode::t ( void ) { // This is a _highly_ useful assertion. assert(mT); return mT ; }
<< vnode methods >>=

#ifdef V_XCLONES

void vnode::setXCloneIndex ( long index ) { mXCloneIndex = index ; }

#endif
<< vnode methods >>=

#ifdef V_XCLONES

void vnode::setXCloneName ( String name ) { mXCloneName = name ; }

#endif
<< vnode methods >>=

void vnode::appendStringToBody ( String s ) { if ( s . Length() == 0 ) return ; String body = this -> bodyString(); body += s ; this -> setBodyStringOrPane ( body ) ; this -> setIcon(); }
<< vnode methods >>=

void vnode::setBodyStringOrPane ( String s ) { Commands * c = this -> commands(); if ( c == NULL ) return ; vnode * current = c -> currentVnode(); if ( current == NULL ) return ;

if ( this == current ) { // Replace the body text with s. TSyntaxMemo * memo = c -> mSyntaxMemo; memo -> Text = s ; }

// Keep the body text in the tnode up-to-date. tnode * t = this -> t(); t -> setTnodeText(s); t -> setSelection(0,0); // 9/27/00 }
<< vnode methods >>=

void vnode::setHeadString ( String s ) { assert(mTreeNode); mTreeNode -> Text = s ; this -> setDirtyJoined(); }

void vnode::initHeadString ( String s ) { assert(mTreeNode); mTreeNode -> Text = s ; }
@ Unlike setBodyStringOrPane, this code will work whether or not the receiver is the presently selected vnode, because the assignment mTreeNode -> Text = s updates the headline.

<< vnode methods >>=

void vnode::setHeadStringOrHeadline ( String s ) { if ( mTreeNode == NULL ) return ; Commands * c = this -> commands(); if ( c == NULL ) return ;

c -> endEditing(); this -> setHeadString( s ); }
@ This trims trailing blank lines from a node. It is surprising difficult to do this during Untangle.

<< vnode methods >>=

void vnode::trimTrailingLines(void) { String s1 = this -> bodyString(); char * s = s1 . c_str(); long len = strlen(s) ; long newlines = 0 ; long ws = 0 ;

for (long i = len - 1; i >= 0 ; -- i ) { char c = s[i] ; if ( is_ws(c) ) { --len, ++ws ; } else if ( c == '\r' ) --len ; else if ( c == '\n' ) { --len ; ++newlines ; } else break ; }

if ( newlines > 1 || newlines > 0 && ws > 0 ) { String s2 ( s, len ) ; this -> setBodyStringOrPane ( ( newlines > 0 ) ? s2 + "\n" : s2 ) ; // Don't set the dirty bit: it would just be annoying. } }
<< vnode methods >>=

long vnode::computeIcon ( void ) { vnode * current = this -> currentVnode(); if ( current == NULL ) return -1 ;

long val = 0 ; if ( this -> t() -> hasBody() ) val += 1 ; if ( this -> isMarked() ) val += 2 ; if ( this -> isCloned() ) val += 4 ; if ( this -> isDirty() ) val += 8 ; return val ; }
@ The caller must call commands -> Repaint().

<< vnode methods >>=

void vnode::setIcon ( void ) { static long guard = 0 ; assert(guard == 0); // This routine should never cause recursion.

long val = this -> computeIcon(); if ( val != -1 ) { // Update the icon. ++guard ; this -> setIconWithVal ( val ); --guard ; } }
@ Calling setIconVal -1 is the idiom to force the icon to be redrawn.

<< vnode methods >>=

void vnode::setIconVal ( long val ) { mIconVal = val ; }
<< vnode methods >>=

void vnode::setIconWithVal ( long val ) { if ( val != mIconVal ) { mIconVal = val ; assert(mTreeNode); mTreeNode -> ImageIndex = val ; mTreeNode -> SelectedIndex = val ; } }
<< vnode methods >>=

void vnode::setJoinList ( vnode * v ) { assert(mJoinList==NULL); mJoinList = v ; }
<< vnode methods >>=

void vnode::setSelection ( int start, int length ) { mT -> setSelection ( start, length ); }
<< vnode methods >>=

void vnode::setT (tnode * t ) { // Bug fix: 7/9/01: do nothing if t == mt (!) if ( t != mT ) { delete mT ; // Delete the tnode created when reading a file. mT = t ; } }
<< vnode methods >>=

void vnode::setTreeNode ( TTreeNode * treeNode ) { assert(treeNode); mTreeNode = treeNode ; }
<< vnode methods >>=

void vnode::clearClonedBit ( void ) { mStatusBits &= ~ clonedBit ; this -> setIcon(); }
<< vnode methods >>=

void vnode::clearDirty ( void ) { mT -> clearDirty() ; this -> setIcon(); }

void vnode::clearDirtyJoined ( void ) { mT -> clearDirty() ; this -> setIcon();

for ( vnode * v2 = this -> joinList(); v2 && v2 != this ; v2 = v2 -> joinList() ) v2 -> setIcon(); }
<< vnode methods >>=

void vnode::clearDummy ( void ) { mStatusBits &= ~ dummyBit ; }
<< vnode methods >>=

void vnode::clearMarked ( void ) { mStatusBits &= ~ markedBit ; this -> setIcon(); }
<< vnode methods >>=

void vnode::clearOrphan ( void ) { mStatusBits &= ~ orphanBit ; }
<< vnode methods >>=

void vnode::clearVisited ( void ) { mStatusBits &= ~ visitedBit ; }
<< vnode methods >>=

void vnode::initClonedBit ( bool val ) { if ( val ) mStatusBits |= clonedBit ; else mStatusBits &= ~clonedBit ; }
<< vnode methods >>=

void vnode::initDirtyBit ( void ) { mT -> setDirty(); }
<< vnode methods >>=

void vnode::initExpandedBit ( void ) { mStatusBits |= expandedBit ; }
<< vnode methods >>=

void vnode::initMarkedBit ( void ) { mStatusBits |= markedBit ; }
<< vnode methods >>=

void vnode::initSelectedBit ( void ) { mStatusBits |= selectedBit ; }
<< vnode methods >>=

void vnode::initTopBit ( void ) { mStatusBits |= topBit ; }
<< vnode methods >>=

void vnode::initStatus ( short status ) { mStatusBits = status ; }
<< vnode methods >>=

void vnode::setAncestorAtFileNodeDirty ( void ) { for ( vnode * v = this ; v ; v = v -> parent() ) { if ( ! v -> isDirty() && v -> isAtFileNode() ) { v -> setDirty(); v -> setDirtyJoined(); } } }
<< vnode methods >>=

void vnode::setClonedBit ( void ) { mStatusBits |= clonedBit ; this -> setIcon(); }
<< vnode methods >>=

void vnode::setDirty ( void ) { mT -> setDirty(); // Prevent recursion below.

// Leo2: set the dirty bits of all ancestor @file nodes. for ( vnode * v = this ; v ; v = v -> parent() ) { if ( ! v -> isDirty() && v -> isAtFileNode() ) { v -> setDirty(); v -> setDirtyJoined(); } }

this -> setIcon(); }

void vnode::setDirtyJoined ( void ) { mT -> setDirty(); this -> setIcon();

for ( vnode * v2 = this -> joinList(); v2 && v2 != this ; v2 = v2 -> joinList() ) v2 -> setDirty(); }
<< vnode methods >>=

void vnode::setDummy ( void ) { mStatusBits |= dummyBit ; }
<< vnode methods >>=

void vnode::setOrphan ( void ) { mStatusBits |= orphanBit ; }
<< vnode methods >>=

void vnode::setVisited ( void ) { mStatusBits |= visitedBit ; }
<< vnode methods >>=

void vnode::setMarked ( void ) { mStatusBits |= markedBit ; this -> setIcon(); }
@ Create a clone of back and insert it as the next sibling of back.

<< vnode methods >>=

vnode * vnode::clone ( vnode * back ) { vnode * clone = this -> cloneTree ( back ) ; clone -> createDependents() ; #if 0 // too slow this -> initAllCloneBits() ; #endif // Set the clone bit in all nodes joined to back. back -> setClonedBit() ; for ( vnode * v = back -> mJoinList ; v && v != back ; v = v -> mJoinList ) v -> setClonedBit() ; return clone ; }
@ This method creates all nodes that depend on the receiver.

<< vnode methods >>=

void vnode::createDependents ( void ) { vnode *v = this ; tnode *t = v -> mT ; vnode *parent = v -> parent(); if ( parent == NULL ) return ;

// Copy v as the nth child of all nodes joined to parent. long n = v -> childIndex() ; vnode *p = parent -> mJoinList ; while ( p && p != parent ) { vnode * copy = p -> insertAsNthChild ( n, t ) ; copy -> initHeadString ( v -> headString() ) ; copyTree( v, copy ); this -> joinTreeTo ( copy ) ; p = p -> mJoinList ; } }
@ This is the main delete routine. It deletes the receiver's entire tree from the screen.

Deleting an outline is actually a tricky operation because of the Undo command. We don't actually delete vnodes (or reduce the refCount field of tnodes) until the delete operation becomes undoable. (This can happen even if unlimited undo/redo is ever implemented.)

Therefore, dvnodes can store a pointer to the tnode containing the body text, and a good thing too, because otherwise undoing a joined node would not rejoin the node.

<< vnode methods >>=

dvnode * vnode::doDelete ( vnode * newVnode ) // delete is a C++ keyword { Commands * commands = this -> commands(); // Needed after 'this' no longer valid. assert(mTreeNode); TTreeNode * treeNode = mTreeNode ; // Will be cleared below.

// Destroy all dependent vnodes & TTreeNodes. this -> destroyDependents() ;

// Create a list of dvnodes containing all info from the outline. dvnode * dv = this -> saveOutlineWithLevel ( 0 ) ; << dump the dv list >>

// Unjoin all nodes of the deleted tree. this -> unjoinTree();

// Destroy the entire tree of TTreeNodes. treeNode -> Delete() ;

// Bug fix: 1/18/99: we must set the currentVnode here! commands -> selectVnode ( newVnode ) ;

// Update all clone bits. commands -> initAllCloneBits();

return dv ; }
@code

TRACEP("vnode::doDelete:dumpDVnodes", ecnl(); es("Dump of dvnodes: "); while ( dv ) { String s = dv -> headline ; ecnl(); epadlong( dv -> level, 2 ); ecs(); es(s.c_str()); dv = dv -> next ; } ecnls(2); );
<< vnode methods >>=

void vnode::freeDVnodes ( dvnode * dv ) { while ( dv ) { dvnode * next = dv -> next ; delete dv ; dv = next ; } }
@ This method inserts a new vnode after the receiver.

<< vnode methods >>=

vnode * vnode::insertAfter ( tnode * t ) { assert(mTreeNode); vnode * back = this ; vnode * next = back -> next() ; vnode * parent = back -> parent() ; if ( t == NULL ) t = new tnode() ; vnode * v = new vnode( this -> mCommands, t ) ; String s ( "NewHeadline" ) ; TTreeNodes * treeNodes = back -> treeNodes() ;

if ( next ) // Insert before next. v -> mTreeNode = treeNodes -> InsertObject( next -> mTreeNode, s, v ) ; else if ( parent ) // Insert as the last child of parent. v -> mTreeNode = treeNodes -> AddChildObject( parent -> mTreeNode, s, v ) ; else // Insert as the first sibling of back. v -> mTreeNode = treeNodes -> AddObject( back -> mTreeNode, s, v ) ;

return v ; }
@ This method inserts a new node as the last child of the receiver.

<< vnode methods >>=

vnode * vnode::insertAsLastChild ( tnode * t ) { long n = this -> numberOfChildren() ; return insertAsNthChild ( n, t ) ; }
@ This method inserts a new vnode as the nth child of the receiver.

<< vnode methods >>=

vnode * vnode::insertAsNthChild ( long index, tnode * t ) { assert(mTreeNode); vnode * parent = this ; if ( t == NULL ) t = new tnode() ; vnode * v = new vnode( this -> mCommands, t ); String s ( "NewHeadline" ) ; TTreeNodes * treeNodes = parent -> treeNodes() ;

if ( index == 0 ) // Insert as the first child of parent. v -> mTreeNode = treeNodes -> AddChildObjectFirst( parent -> mTreeNode, s, v ) ; else if ( index >= parent -> numberOfChildren() ) // bug fix: 8/28/99 (was index+1) // Insert as the last child of the parent. v -> mTreeNode = treeNodes -> AddChildObject( parent -> mTreeNode, s, v ) ; else { // Insert before the child index . vnode * child = parent -> nthChild ( index ) ; assert(child); v -> mTreeNode = treeNodes -> InsertObject( child -> mTreeNode, s, v ) ; }

return v ; }
@ This method moves the receiver after node v.

<< vnode methods >>=

void vnode::moveAfter (vnode * v ) { assert(mTreeNode); vnode * next = v -> next() ; vnode * parent = v -> parent() ;

this -> destroyDependents() ;

if ( next ) // Move _before_ next. (The documentation is incorrect.) mTreeNode -> MoveTo ( next -> mTreeNode, naInsert ) ; else if ( parent ) // Move to the last child of parent. mTreeNode -> MoveTo ( parent -> mTreeNode, naAddChild ) ; else // Move to the last node. mTreeNode -> MoveTo ( NULL, naAdd ) ;

this -> createDependents() ; }
@ This method moves the receiver before node v.

<< vnode methods >>=

void vnode::moveToRoot ( void ) { assert(mTreeNode); this -> destroyDependents() ; mTreeNode -> MoveTo ( NULL, naAddFirst ) ; this -> createDependents() ; }
<< vnode methods >>=

void vnode:: moveToNthChildOf (vnode * parent, long index ) { assert(mTreeNode); this -> destroyDependents() ;

// Move the tree. if ( index == 0 ) mTreeNode -> MoveTo ( parent -> mTreeNode, naAddChildFirst ) ; else if ( index + 1 >= parent -> numberOfChildren() ) // Move to the last child of the parent. mTreeNode -> MoveTo ( parent -> mTreeNode, naAddChild ) ; else { // Move before the child with index + 1. vnode * child = parent -> nthChild ( index + 1 ) ; assert(child); mTreeNode -> MoveTo ( child -> mTreeNode, naInsert ) ; }

this -> createDependents() ; }
@ This method restores the tree described by dv in the position described by back and parent. If back exists, we insert the tree after back. Otherwise, if parent exists we insert the tree as the first child of parent. If both back and parent are null the new tree becomes the root vnode.

<< vnode methods >>=

vnode * vnode::restoreOutlineFromDVnodes ( dvnode * dv, vnode * parent, vnode * back ) { Commands * commands = this -> commands(); TTreeNodes * treeNodes = this -> treeNodes() ; vnode * result = NULL ;

while ( dv ) { String s = dv -> headline ; vnode *v = dv -> v ; if ( result == NULL ) result = v; << Insert v into the outline >> if ( dv -> next ) { << Set back and parent fields for the new vnode >> } dv = dv -> next ; } << set icons for all vnodes of the result tree >> return result ; }
@code

if ( back ) { vnode * next = back -> next(); if ( next ) { // Insert v before next. v -> mTreeNode = treeNodes -> InsertObject( next -> mTreeNode, s, v ) ; } else { // Insert v as the last sibling of back. v -> mTreeNode = treeNodes -> AddObject( back -> mTreeNode, s, v ) ; } } else if ( parent ) // Insert v as the first child of parent. v -> mTreeNode = treeNodes -> AddChildObjectFirst( parent -> mTreeNode, s, v ) ; else { // Insert v as the root node. vnode * root = commands -> rootVnode(); v -> mTreeNode = treeNodes -> InsertObject( root -> mTreeNode, s, v ) ; }
@code

long delta = dv -> next -> level - dv -> level ;

if ( delta > 0 ) { assert(delta==1); back = NULL ; parent = v ; } else if ( delta == 0 ) { back = v ; parent = back -> parent(); } else { while ( ++delta < 0 && parent ) parent = parent -> parent(); back = parent ; if ( back ) parent = back -> parent(); }
@ New code: 8/8/00 @code

vnode * next = result -> nodeAfterTree(); for ( vnode * v = result ; v && v != next ; v = v -> threadNext() ) { v -> setIconVal(-1); // Disable usual optimization. v -> setIcon(); }
@ This method creates a cloned tree after oldTree.

<< vnode methods >>=

vnode * vnode::cloneTree ( vnode * oldTree ) { // Create a new tree following oldTree. vnode * newTree = this -> insertAfter ( oldTree -> mT ) ; newTree -> initHeadString ( oldTree -> headString() ) ; copyTree ( oldTree, newTree ) ;

// Join the trees and copy clone bits. oldTree -> joinTreeTo ( newTree ) ; oldTree -> copyCloneBitsTo ( newTree ) ;

// 9/19/00 vnode * next = newTree -> nodeAfterTree(); for ( vnode * v = newTree ; v && v != next ; v = v -> threadNext() ) { v -> setIconVal(-1); // Disable usual optimization. v -> setIcon(); }

return newTree ; }
@ This methods propagates clone bits from the receiver's tree to tree2.

<< vnode methods >>=

void vnode::copyCloneBitsTo ( vnode * tree2 ) { vnode * tree1 = this ; ASSERT(tree2);

// Set the bit in the root. if ( tree1 -> isCloned() ) tree2 -> setClonedBit(); else tree2 -> clearClonedBit();

// Recursively set the bits in all subtrees. vnode *child1 = tree1 -> firstChild() ; vnode *child2 = tree2 -> firstChild() ; while (child1) { PERM_ASSERT(child2); if ( child1 -> isCloned() ) child2 -> setClonedBit(); else child2 -> clearClonedBit(); child1 = child1 -> next() ; child2 = child2 -> next() ; } PERM_ASSERT(child2 == NULL); }
@ This method copies all subtrees of oldRoot to the subtrees of newRoot. The caller is responsible for copying the headline text from oldRoot to newRoot.

This method must be given the new root as well as the old: the Borland classes do not allow us to create an unattached outline.

<< vnode methods >>=

void vnode::copyTree( vnode * oldTree, vnode * newTree ) { vnode * old_v = oldTree -> firstChild(); if ( old_v == NULL ) return ;

// Copy the first child of oldTree to the first child of newTree. vnode * new_v = newTree -> insertAsNthChild ( 0, old_v -> mT ) ; new_v -> initHeadString ( old_v -> headString() ) ;

// Copy all other children of oldTree after the first child of newTree. for ( old_v = old_v -> next() ; old_v ; old_v = old_v -> next() ) { new_v = new_v -> insertAfter ( old_v -> mT ) ; new_v -> initHeadString ( old_v -> headString() ) ; }

// Recursively copy all descendents of oldTree. new_v = newTree -> firstChild() ; old_v = oldTree -> firstChild() ; while ( old_v ) { assert ( new_v ) ; copyTree ( old_v, new_v ) ; old_v = old_v -> next(); new_v = new_v -> next(); } assert ( new_v == NULL ) ; }
@ This method destroys all dependent vnodes and TTreeNode's associated with the receiver.

<< vnode methods >>=

void vnode::destroyDependents ( void ) { vnode * parent = this -> parent() ; if ( parent == NULL ) return ;

// Destroy the nth child of all nodes joined to the receiver's parent. long n = this -> childIndex(); vnode * join = parent -> mJoinList ; while ( join && join != parent ) { vnode *child = join -> nthChild ( n ) ; if ( child ) { child -> unjoinTree() ; child -> destroyTree() ; } join = join -> mJoinList ; } }
@ This method destroys (irrevocably deletes) a vnode tree.

This code should be called only when it is no longer possible to undo a previous delete. It is always valid to destroy dependent trees.

<< vnode methods >>=

void vnode::destroyTree ( void ) { assert(mTreeNode); TTreeNode * treeNode = this -> mTreeNode ; // Will be cleared below.

// Delete all the vnodes of the tree. vnode * v = this ; vnode * after = this -> nodeAfterTree(); while ( v && v != after ) { vnode * next = v -> threadNext() ; delete v ; v = next ; }

// Destroy the entire tree of TTreeNodes. treeNode -> Delete() ; }
<< vnode methods >>=

void vnode::invalidOutline ( String message ) { vnode * parent = this -> parent() ; String s("invalid outline: ") ; s += message ; s += '\r' ;

if ( parent ) s += parent -> description() ; else s += this -> description() ;

alert ( s . c_str() ) ; }
<< vnode methods >>=

bool vnode::isJoinedTo ( vnode * v ) { return v && this -> mT == v -> mT ; }
@ This function returns true if the nodes v1 and v2 are on the same join list.

<< vnode methods >>=

bool vnode::isOnJoinListOf ( vnode *v2) { vnode * v1 = this ; assert( v2 && v1 -> mT && v2 -> mT );

// v1 and v2 must share the same tnode. if ( v1 -> mT != v2 -> mT ) return FALSE;

// v1 and v2 must have join lists. if ( v1 -> mJoinList == NULL || v2 -> mJoinList == NULL ) return FALSE;

// Return TRUE if v2 is on v1's join list. for ( vnode * v = v1 -> mJoinList ; v && v != v1 ; v = v -> mJoinList ) if ( v == v2 ) return TRUE;

return FALSE; }
@ This method joins the receiver to v2 if the two nodes have not already been joined.

Joining involves placing each vnode on the others join list.

<< vnode methods >>=

void vnode::joinNodeTo ( vnode * v2 ) { vnode *v1 = this ; if ( v1 -> isOnJoinListOf ( v2 ) ) return ; // Bug fix: 7/9/01 ??? (was v1)

vnode *j1 = v1 -> mJoinList ; vnode *j2 = v2 -> mJoinList ;

if (j1 && j2) { // Swapping pointers joins the two cycles. v1 -> mJoinList = j2; // Neither join list is NULL. v2 -> mJoinList = j1; } else if (j1) { v2 -> mJoinList = j1; // Link v2 after v1. v1 -> mJoinList = v2; } else if (j2) { v1 -> mJoinList = j2; // Link v1 after v2. v2 -> mJoinList = v1; } else { v1 -> mJoinList = v2; // point v1 and v2 at each other. v2 -> mJoinList = v1; }

#if 0 v1 -> setDirty(); v2 -> setDirty(); #endif

PERM_ASSERT( v1 -> mJoinList && v2 -> mJoinList ); }
@ This function joins all nodes in the receiver and tree2. This code makes no assumptions about the two trees, and some or all of the nodes may already have been joined. The PERM_ASSERT's guarantee that both trees have the same topology.

<< vnode methods >>=

void vnode::joinTreeTo ( vnode *tree2 ) { vnode * tree1 = this ; ASSERT(tree2);

// Join the roots. tree1 -> joinNodeTo ( tree2 ) ;

// Recursively join all subtrees. vnode *child1 = tree1 -> firstChild() ; vnode *child2 = tree2 -> firstChild() ; while (child1) { PERM_ASSERT(child2); child1 -> joinTreeTo ( child2 ) ; child1 = child1 -> next() ; child2 = child2 -> next() ; } PERM_ASSERT(child2 == NULL); }
@ This method is called just before destroying a Tree of TTreeNodes. It copies the data into the dvnode so the headlines can be restored if the deleted is undone.

<< vnode methods >>=

dvnode * vnode::saveOutlineWithLevel ( long level ) { vnode * v = this ; dvnode * root = new dvnode() ; dvnode * dv = root ; << Set the data fields of dv >>

vnode * child = this -> firstChild(); while ( child ) { // Recursively create a list of dvnodes for child's subtree. dv -> next = child -> saveOutlineWithLevel ( level + 1 ) ; // Move dv to the end of the list of dvnodes. while ( dv && dv -> next ) dv = dv -> next ; child = child -> next(); } return root ; }
@code

dv -> next = NULL ; dv -> level = level ; dv -> headline = v -> headString(); dv -> v = v ;
@ This function returns true if the receiver should be a clone. This can be done _very_ quickly using the receiver's join list.

The receiver is a clone if and only it is structurally _dissimilar_ to a node joined to it. Structurally _similar_ joined nodes have non-null, distinct and joined parents, and have the same child indices.

9/16/99 We now return the node that proves that the receiver should be a clone. This allows us to dispense with the old "survivor" logic in commands::deleteHeadline.

<< vnode methods >>=

bool vnode::shouldBeClone ( void ) { vnode *p = this -> parent() ; long n = this -> childIndex();

for ( vnode * v = this -> mJoinList ; v && v != this ; v = v -> mJoinList ) { vnode * vp = v -> parent() ; if ( p == NULL || vp == NULL || // Nodes joined to top level nodes are clones. vp == p || // Joined nodes with the same parent are clones. ! p -> isJoinedTo ( vp ) || // Nodes with unjoined parents are clones. v -> childIndex() != n // Nodes with different child indices are clones. ) return TRUE; // The receiver is structurally _dissimilar_ to v. }

// The receiver is structurally similar to all nodes joined to it. return FALSE ; }
@ This code carefully unlinks the receiver from its join list. We can not simply assume that all such links will eventually be cleared.

<< vnode methods >>=

void vnode::unjoinNode ( void ) { vnode * next = this -> mJoinList ; if ( next == NULL ) return ;

if ( next -> mJoinList == this ) { // The list contains only two elements. next -> mJoinList = NULL ; this -> mJoinList = NULL ; #if 0 this -> setDirty() ; next -> setDirty() ; #endif } else { vnode * prev = NULL ; << Set prev to the node that points to this >> // Remove this from the join list. prev -> mJoinList = next; this -> mJoinList = NULL; #if 0 this -> setDirty() ; prev -> setDirty() ; #endif } }
@ We guard against any cycles in the join list, which would cause this loop to hang. It's much better to cause an assert to fail. @code

Commands * commands = this -> commands() ; commands -> clearAllVisited();

for ( prev = next; prev && prev -> mJoinList != this ; prev = prev -> mJoinList ) if ( prev -> isVisited() ) assert ( 0 ) ; else prev -> setVisited() ;
@ This function unjoins all nodes of the receiver's tree.

<< vnode methods >>=

void vnode::unjoinTree ( void ) { vnode * v = this ; vnode * after = this -> nodeAfterTree() ;

while ( v && v != after ) { v -> unjoinNode() ; v = v -> threadNext() ; } }
@ This routine checks the structure of the receiver's tree.

<< vnode methods >>=

bool vnode::validateOutlineWithParent ( vnode * p ) { bool result = TRUE ; // optimists get only unpleasant surprises. vnode * parent = this -> parent() ; long childIndex = this -> childIndex() ;

<< validate parent ivar >> << validate childIndex ivar >> << validate x ivar >>

// Recursively validate all the children. for ( vnode * child = this -> firstChild(); child; child = child -> next() ) { bool r = child -> validateOutlineWithParent ( this ) ; if ( r == FALSE ) result = FALSE ; }

return result ; }
@code

if ( parent != p ) this -> invalidOutline ( "Invalid parent link: " + parent -> description() ) ;
@code

if ( p ) { if ( childIndex < 0 ) this -> invalidOutline ( "missing childIndex" + childIndex ) ;

else if ( childIndex >= p -> numberOfChildren() ) this -> invalidOutline ( "missing children entry for index: " + childIndex ) ; } else if ( childIndex < 0 ) this -> invalidOutline ( "negative childIndex" + childIndex ) ;
@code

if ( this -> t() == NULL && p != NULL ) this -> invalidOutline ( "NULL t" ) ;
#include <stdio.h>

class vnode ; class Commands ;

<< declare public utility functions >>

class atFile { public: atFile (Commands * commands) ; void read (vnode * v) ; void readAll (vnode * v) ; void write (vnode * v) ; void writeAll (vnode * v) ; private: << private atFile data >> << private read methods >> << private sentinel methods >> << private utilities >> << private write methods >> };
bool compareFiles ( String path1, String path2 ) ; short directiveKind (char * ip, char * limit) ; bool isSectionName ( char *ip, char *limit, char **end_pp ) ; bool matchHeadline ( vnode * v, String pattern ) ; void setAncestorAtFileNodeDirty ( vnode * v ) ;
@ The commander for the current window. @c

Commands * mCommands ;

@ mErrors is number of errors seen while reading and writing. We no longer make a distinction between warnings and errors, because there is no problem that can result in lost data. mStructureErrors are errors reported by createNthChild. If structure errors are found we delete the outline tree and rescan. @c

long mErrors ; long mStructureErrors ;

@ The files used by the output routines. When tangling, we first write to a temporary output file. After tangling is complete we compare the temporary file to the target file, if it exists. If the files are equal we just delete the temporary file. Otherwise we delete the old target file and rename the temporary file to be the target file. @c

String mTargetFileName ; String mOutputFileName ;

@ The stream used to write the temporary output file. @c

TFileStream * mOutputStream ;

@ The indentation used when outputting section references or at-others sections. We add the indentation of the line containing to reference or at-others to indent before expansion, then restore indent to its previous value when expansion is complete.

The unit of indentation is spaces, not tabs. @c

long indent ;

@ The strings to be output before and after each sentinel line, i.e., the strings used to specify the beginning and end of sentinel comments. mEndSentinelComment is empty for single-line comments.

When writing, we set these strings to the comments delimiters specified by the Preferences Panel and any applicable @language and @comment directives, with single-line comment delimiters being used by default. When reading, we set the strings using the characters before and after the <leo> tag in the first line of the file. @c

String mStartSentinelComment ; String mEndSentinelComment ;

@ The root of tree being written. @c

vnode * mRoot ;
@code

vnode * createNthChild (long n, vnode * parent, String headline) ; void joinTrees ( vnode *tree1, vnode *tree2 ) ; char * scanDoc (TMemoryStream * ms, char *ip, char *limit, short kind) ; char * scanHeader ( char * ip, char * limit ) ; char * scanText (vnode *v, TMemoryStream *ms, char *ip, char *limit, short endSentinelKind) ; void updateCloneIndices ( vnode * root, vnode * next ) ;
@code

String nodeSentinelText ( vnode * v ) ;

void putCloseNodeSentinel ( vnode * v ) ; void putCloseSentinels ( vnode * root, vnode * v ) ;

void putOpenLeoSentinel ( String text ) ; void putOpenNodeSentinel ( vnode * v ) ; void putOpenSentinels ( vnode * root, vnode * v ) ;

void putSentinel ( String text ) ;

short sentinelKind ( char *ip, char *limit ) ; String sentinelName ( short sentinelKind ) ;

char * skipSentinelStart( char *ip, char *limit) ;
@code

void appendToBody (TMemoryStream *ms, char *start, char *end); void appendToBody (TMemoryStream *ms, String s); void error ( String message ); void scanAllDirectives ( vnode * v ); char * skipIndent (long n, char * ip, char * limit); void writeError ( String message );
@code

// Top level... vnode * findReference (vnode *root, char *ip, char *end_p, char *limit ) ; bool inAtOthers (vnode * v) ; void putAtOthers ( vnode * v, long delta ) ; void putAtOthersChild ( vnode * v ) ; void putBody ( vnode * root, vnode * v, long delta ) ; void putBodyPart ( vnode * v ) ; char * putCodePart ( vnode * v, char * ip, char * limit ) ; char * putDirective ( char * ip, char * limit ) ; char * putDoc ( char *ip, char *limit, short kind ) ; char * putDocPart ( char * ip, char * limit ) ;

// Output methods... void putBlanks (int n) ; void putChar (char c) ; void putIndent (int n) ; // Outputs tabs corresponding to n spaces. void putInt (int n) ; char * putLeadingWs ( char * ip, char * limit_p ) ; void putString ( String s ) ; void putTabs (int n) ;
// atFile.cpp created June 6, 2001.

#pragma option -w-par // no warning: "Parameter is never used" #pragma option -w-sig // no warning: "Conversion may lose significant digits"

<< atFile includes >> << atFile abbreviations >> << atFile constants >>

@others
#include <vcl.h> #pragma hdrstop

#include "args.h" #include "C_Tangle.h" // for some globals. #include "Commands.h" #include "hash.h" #include "MainUnit.h" // for alert(). #include "atFile.h" #include "tnode.h" #include "Utils.h" #include "vnode.h"

#include <io.h> // for creattemp(). #include <ctype.h> #include <dir.h> #include <stdlib.h> #include <Filectrl.hpp> // For DirectoryExists.

#include "Leo.h"
#define oblank() this -> putBlanks(1) #define oblanks(n) this -> putBlanks(n) #define ochar(c) this -> putChar(c) #define oint(n) this -> putInt(n) #define onl() this -> putChar('\n') #define os(s) this -> putString(s) #define otab() this -> putTabs(1) #define otabs(n) this -> putTabs(n)
@ These constants indicate the kind of at_directives. @c

enum { noDirective, // not an at-directive. docDirective, // @doc. atDirective, // @<space> or @<newline> codeDirective, // @code cDirective, // @c<space> or @c<newline> othersDirective, // at-others miscDirective // All other directive };

@ These constants indicate the kind of sentinel line. @c

enum { noSentinel, // Not a sentinel startAt, endAt, //@+at, @-at startBody, endBody, //@+body,@-body startDoc, endDoc, //@+doc, @-doc startLeo, endLeo, // @+leo, @-leo startNode, endNode, //@+node, @-node startOthers, endOthers, //@+others, //@-others startRef, //@ < < ... @ > > startVerbatim, //@verbatim startDirective //@@ };
void Commands::readAtFile ( vnode * v ) { atFile * at = new atFile( this ); at -> read ( v ) ; delete at ; }
void Commands::writeAtFile ( vnode * v ) { atFile * at = new atFile( this ); at -> write ( v ) ; delete at ; }
atFile::atFile( Commands * theCommander ) { // Inialize ivars. mCommands = theCommander ; mErrors = 0 ; mStructureErrors = 0 ; mTargetFileName = "" ; mOutputFileName = "" ; mOutputStream = NULL ; indent = 0 ; mStartSentinelComment = "//" ; mEndSentinelComment = "" ; mRoot = NULL ; }
@ Sections appear in the derived file in reference order, not tree order. Therefore, when we insert the nth child of the parent there is no guarantee that the previous n-1 children have already been inserted. And it won't work just to insert the nth child as the last child if there aren't n-1 previous siblings. For example, if we insert the third child followed by the second child followed by the first child the second and third children will be out of order.

To ensure that nodes are placed in the correct location we create "dummy" children as needed as placeholders. In the example above, we would insert two dummy children when inserting the third child. When inserting the other two children we replace the previously inserted dummy child with the actual children.

vnode child indices are zero-based. Here we use 1-based indices.

With the "mirroring" scheme it is a structure error if we ever have to create dummy vnodes. Such structure errors cause a second pass to be made, with an empty root. This second pass will generate other structure errors, which are ignored. @c

vnode * atFile::createNthChild(long n, vnode * parent, String headline) { assert(n > 0); vnode * result = NULL ;

// Create any needed dummy children. long dummies = n - parent -> numberOfChildren() - 1 ; if ( dummies > 0 ) ++ mStructureErrors ;

while (dummies-- > 0) { vnode * dummy = parent -> insertAsLastChild(new tnode()) ; // The user should never see this headline. dummy -> initHeadString ( "Dummy" ) ; }

if (n <= parent -> numberOfChildren()) { result = parent -> nthChild(n-1); if ( headline.Trim() != result -> headString() . Trim() ) ++ mStructureErrors ; } else { ++ mStructureErrors ; result = parent -> insertAsLastChild(new tnode()) ; }

result -> initHeadString ( headline ) ; return result ; }
@ This function joins all nodes in the two trees which should have the same topology. This code makes no other assumptions about the two trees; some or all of the nodes may already have been joined.

There are several differences between this method and the similar vnode:joinTreeTo method. First, we can not assert that the two trees have the same topology because the derived file could have been edited outside of Leo. Second, this method also merges the tnodes of all joined nodes. @c

void atFile::joinTrees ( vnode *tree1, vnode *tree2 ) { assert(tree1 && tree2); // Use a common tnode for both nodes. if ( tree1 -> t() != tree2 -> t() ) tree1 -> setT ( tree2 -> t() ) ; // Join the roots using the vnode class. tree1 -> joinNodeTo ( tree2 ) ; // Recursively join all subtrees. vnode *child1 = tree1 -> firstChild() ; vnode *child2 = tree2 -> firstChild() ; while (child1 && child2) { joinTrees ( child1, child2 ) ; child1 = child1 -> next() ; child2 = child2 -> next() ; } if (child1 || child2) error("cloned nodes have different topologies"); }
@ This is the entry point to the read code. The root vnode should be an @file node.

If there are structure errors the first time we delete root and its body text, then rescan. All other errors indicate potentially serious problems with sentinels.

The caller has enclosed this code in BeginUpdate/EndUpdate. @c

void atFile::read ( vnode * root ) { << Declare read vars >> mCommands -> endEditing(); // Capture the current headline. mCommands -> clearAllVisited() ; // Clear the list of nodes for orphans logic. mTargetFileName = root -> atFileNodeName(); mRoot = root ; << read the entire file into fileData >> if ( mErrors > 0 ) return ; << Scan the file buffer >> vnode *next = root -> nodeAfterTree(); if ( mStructureErrors > 0 ) { mErrors = 0 ; << Delete root's tree and body text >> << Scan the file buffer >> } if ( mErrors == 0 ) { if ( mStructureErrors > 0 ) { << Issue structure error message >> } << Handle clone bits >> << Join cloned trees >> << Handle all status bits >> } else { << Issue serious error message >> } }
String fileData(""); hashTable * h = NULL ; TMemoryStream * ms = NULL ; char *buf = NULL ; char *ip = NULL ; char *limit = NULL ;
@c

if ( mTargetFileName . IsEmpty() ) error("Missing file name in @file node"); else if ( FileExists ( mTargetFileName ) ) { TMemoryStream * ms = new TMemoryStream(); try { ms -> LoadFromFile(mTargetFileName); fileData = String ( (char *) ms -> Memory, ms -> Size - 1 ) ; buf = fileData . c_str() ; } catch (...) { error("Can not open " + mTargetFileName) ; } delete ms ; } else error ( mTargetFileName + " does not exit ");
indent = 0 ; ip = buf ; limit = ip + fileData.Length() ; ms = new TMemoryStream() ; // Must use new.

ip = scanHeader(ip, limit); ip = scanText(root, ms, ip, limit, endLeo);

String s( (char *) ms -> Memory, ms -> Size); root -> setBodyStringOrPane(s); delete ms ;
es("----- Minor error reading: "); es(mRoot -> headCString()); es(":marks have been cleared."); enl();
while ( root -> firstChild() ) root -> firstChild() -> doDelete(root);

root -> setBodyStringOrPane ( "" ) ;
@code

mCommands -> clearAllVisited() ;

h = new hashTable() ;

for (vnode *v = root ; v && v != next ; v = v -> threadNext() ) { long cloneIndex = v -> t() -> cloneIndex() ;

// Special case: the root's cloneIndex field contains the max clone index. if ( v != root && cloneIndex > 0 ) { String key = h -> longToString ( cloneIndex ) ; unsigned long hash = h -> computeHash( key ) ; tnode * t = (tnode *) (h -> lookup ( key, hash )); if ( t ) { // v is a clone: share the previous tnode. v -> setT(t); t -> setVisited(); // We will mark these clones later. } else h -> enter ( key, v -> t(), hash ) ; } }

delete h ;

// Set clone marks for all visited tnodes. for (vnode *v = root ; v && v != next ; v = v -> threadNext() ) if ( v -> t() -> isVisited() ) v -> initClonedBit(TRUE);
@code

h = new hashTable() ;

for (vnode *v = root ; v && v != next ; v = v -> threadNext() ) { long cloneIndex = v -> t() -> cloneIndex() ;

// Special case: the root's cloneIndex field contains the max clone index. if ( v != root && cloneIndex > 0 ) { String key = h -> longToString ( cloneIndex ) ; unsigned long hash = h -> computeHash( key ) ; vnode * clone = (vnode *) (h -> lookup ( key, hash )); if ( clone ) this -> joinTrees ( clone, v ) ; // Enter v so we can join the next clone to it. // The next call to lookup will find this v, not the previous. h -> enter ( key, v, hash ) ; } }

delete h ;
vnode *current = NULL ;

for (vnode *v = root ; v && v != next ; v = v -> threadNext() ) { v -> setIcon(); if ( v -> isExpandedBitSet() ) v -> treeNode() -> Expand ( FALSE ) ; else v -> treeNode() -> Collapse ( FALSE ) ; if ( v -> isSelectedBitSet() ) current = v ; if ( v -> isTopBitSet() ) { // Just tell the open code we have seen the top vnode. mCommands -> mTopVnode = v ; } }

if ( current ) { // Indicate what the current node will be. mCommands -> setCurrentVnode ( current ) ; #if 0 // old code mCommands -> makeVisible(current); mCommands -> setCurrentVnode ( NULL ) ; mCommands -> selectVnode ( current ) ; #endif }
es("----- Serious error reading: "); es(mRoot -> headCString()); enl(); es("----- Outline may be corrupted due to damaged sentinel lines!"); enl();
@ This method scans all vnodes, calling read for every @file node found. v should point to the root of the entire tree on entry. @c

void atFile::readAll ( vnode * root ) { vnode * v = root ; while (v) { if ( v -> isAtIgnoreNode() ) v = v -> nodeAfterTree(); else if ( v -> isAtFileNode() ) { if ( ! v -> isOrphan() ) this -> read(v) ; v = v -> nodeAfterTree(); } else v = v -> threadNext(); }

// Clear all dummy and orphan status bits. v = root ; while (v) { v -> clearDummy(); v -> clearOrphan(); v = v -> threadNext(); } }

@ This method scans the doc part and appends the text to v's body. The caller has already appended @<space> or @doc. @c

char * atFile::scanDoc ( TMemoryStream *ms, char *ip, char *limit, short kind ) { short endKind = kind == startDoc ? endDoc : endAt ; bool single = mEndSentinelComment . IsEmpty(); << Skip the opening sentinel >> << Skip an opening block delim >> while (ip < limit && sentinelKind(ip, limit) != endKind ) { << Append the next comment line to v's body >> } << Skip the closing sentinel >> << Remove a closing block delim from v's body >> return ip ; }
assert(match(ip, limit, kind == startDoc ? "+doc" : "+at")); ip = skip_line(ip, limit); this -> appendToBody(ms, kind == startDoc ? "@doc" : "@");
if ( ! single ) { char *ip1 = skip_ws(ip, limit); if (match(ip1, limit, mStartSentinelComment.c_str())) ip = skip_line(ip, limit); }
char *ip2 = skip_to_end_of_line(ip, limit); // End of line.

// Point ip to the start of the real line. if ( single ) { ip = skip_ws(ip, limit); if ( match(ip, limit, mStartSentinelComment.c_str()) ) { // Skip the opening comment delim and a blank. ip += mStartSentinelComment . Length(); if (match(ip, limit, " ")) ++ip; } } else ip = skipIndent(indent, ip, limit);

// Append the line. String line (ip, ip2-ip); String trim = line . TrimRight(); this -> appendToBody(ms, line);

// Append a newline if it is real. if ( ip2 < limit && is_nl(*ip2) && line == trim) this -> appendToBody(ms, "\n");

// Finish the line. ip = skip_line(ip, limit);
if ( sentinelKind(ip, limit) == endKind ) { ip = skipSentinelStart(ip, limit); ip = skip_line(ip, limit); } else error("Missing " + sentinelName(endKind) + " sentinel");
@ This code will typically only be executed for HTML files. @c

if ( ! single ) { // Get what we have written so far. String s( (char *) ms -> Memory, ms -> Size);

// Remove the ending block delimiter. // The delimiter includes leading and trailing newlines. String delim("\n"); delim += mEndSentinelComment ; delim += "\n" ; long slen = s.Length(), dlen = delim.Length(); if ( slen > dlen ) { char * cs = s.c_str(); char * ds = delim.c_str(); if (strncmp(cs + slen - dlen, ds, dlen) == 0) { s = String(cs, slen - dlen); // Rewrite the stream. ms -> Clear(); ms -> Write ( s.c_str(), s.Length() ) ; } } }
@ This method sets mStartSentinelComment and mEndSentinelComment based on the first @+leo sentinel line of the file. We can not call sentinelKind here because that depends on the comment delimiters we set here. @c

char * atFile::scanHeader ( char * ip, char * limit ) { bool valid = TRUE ; char *tag = "@+leo" ;

// Skip blank lines. ip = skip_ws_and_nl(ip, limit); char *ip0 = ip ; char *ip1 = ip ;

// The opening comment delim is the initial non-whitespace. while (ip < limit && !match(ip,limit,tag) && !is_ws(*ip) && !is_nl(*ip)) ++ip ; if (ip1 < ip ) mStartSentinelComment = String (ip1, ip - ip1 ) ; else valid = FALSE ;

// Make sure we have @+leo ip = skip_ws(ip, limit); if ( match(ip, limit, tag)) ip += strlen(tag) ; else valid = FALSE ;

// The closing comment delim is the trailing non-whitespace. ip1 = ip = skip_ws(ip, limit); while (ip < limit && !is_ws(*ip) && !is_nl(*ip)) ++ip ; mEndSentinelComment = String (ip1, ip - ip1);

if ( valid ) ip = skip_line(ip0, limit); else error("Bad @+leo sentinel in " + mTargetFileName);

return ip ; }
@ This method is the heart of the new read code. It scans text until the given ending sentinel is found, and warns if any other ending sentinel is found instead. It calls itself recursively to handle most nested sentinels. @c

char * atFile::scanText ( vnode *v, // The vnode whose body we are accumualting. TMemoryStream *ms, // The stream used to accumulate the body. char *ip, // Pointer into the file buffer. char *limit, // The end of the file buffer. short endSentinelKind ) // The ending sentinel we expect. { long lineIndent = 0 ; // Changed only for sentinels. while (ip < limit) { short kind = sentinelKind(ip, limit) ; char *linep = ip ; if ( kind != noSentinel ) { << set lineIndent and linep >> ip = this -> skipSentinelStart(ip, limit); } switch (kind) { case noSentinel: << remove indent and append line >>break ; case startAt: << scan @+at >> break ; case startBody: << scan @+body >>; break ; case startDirective: << scan @@ >> break ; case startDoc: << scan @+doc >> break ; case startLeo: << scan @+leo >> break ; case startNode: << scan @+node >> break ; case startOthers: << scan @+others >> break ; case startRef: << scan @ref >> break ; case startVerbatim: << scan @verbatim >> break ; case endAt: case endBody: case endDoc: case endLeo: case endNode: case endOthers: << handle an ending sentinel >> break ; default: << warn about unknown sentinel >> } } << handle unexpected end of text >> return ip ; }
@ lineIndent is the total indentation on a sentinel line. The first "indent" portion of that must be removed when recreating text. linep points to the first "real" character of a line, the character following the "indent" whitespace. @c

{ // All sentinels except +leo should start with a newline. char *ip1 = ip ; if ( is_nl(*ip1) ) ip1 = skip_nl(ip1, limit);

linep = skipIndent ( indent, ip1, limit ) ;

// Calculate the total indentation on the line. long n = 0 ; while (ip1 < limit) { if (*ip1 == '\t') n += ( arg_tab_width - n % arg_tab_width ) ; else if (*ip1 == ' ') ++ n ; else break ; ++ ip1 ; }

lineIndent = n ; }
@ The sentinelKind routine returned noSentinel, so if the line contains only a newline that newline is not part of a sentinel and it must be appended to make progress. Otherwise, we don't want to append the trailing newline here because it might be part of a sentinel. @c

{ char *ip1 = skipIndent ( indent, ip, limit ) ; // Append only newlines that can't be part of a sentinel. if (ip < limit && is_nl(*ip)) ip = skip_nl(ip, limit); else ip = skip_to_end_of_line(ip, limit); String line (ip1, ip-ip1); this -> appendToBody(ms, line); }
ip = skip_line(ip, limit); // Skip the sentinel.

if (kind == endSentinelKind) { if (kind == endLeo) { ip = skip_ws_and_nl(ip,limit); if (ip < limit) error("Ignoring text after @-leo"); } return ip ; } else { // Tell of the structure error. String name = sentinelName(kind); String expect = sentinelName(endSentinelKind); error( "Ignoring " + name + " sentinel. Expecting " + expect); }
// Issue the error. String name = sentinelName(endSentinelKind); error("Unexpected end of file. Expecting " + name + "sentinel" );
assert(match(ip,limit,"+at")); ip = scanDoc(ms, ip, limit, kind);
assert(match(ip,limit,"+body")); ip = skip_line(ip, limit); // Skip the sentinel. ip = scanText(v, ms, ip, limit, endBody );
assert(match(ip,limit,"+doc")); ip = scanDoc(ms, ip, limit, kind);
assert(match(ip,limit,"+leo")); ip = skip_line(ip, limit); error("Ignoring unexpected @+leo sentinel" );
{ long n = 0 ; // Default for bad index. String headline (""), ref(""); tnode dummy_t(0,""); vnode dummy(mCommands, &dummy_t) ; // A dummy vnode to carry status bits. << skip the node sentinel, setting headline, ref and n >> long cloneIndex = dummy . t() -> cloneIndex(); long oldIndent = indent ; indent = lineIndent ; if ( n == 0 ) { << Check the filename in the sentinel >> // Put the text of the root node in the current node. ip = scanText(v, ms, ip, limit, endNode ); v -> t() -> setCloneIndex ( cloneIndex ) ; } else { TMemoryStream * new_ms = new TMemoryStream() ; // Must use new. vnode *child = createNthChild(n, v, headline); child -> t() -> setCloneIndex ( cloneIndex ) ; ip = scanText(child, new_ms, ip, limit, endNode ); // If text followed the section reference in the outline, // that text will immediately follow the //@-node sentinel. if (ip < limit && !is_nl(*ip)) { char *ip1 = ip; ip = skip_line(ip, limit); String line (ip1, ip-ip1); this -> appendToBody(ms, line); } << copy new_ms to child's body >> delete new_ms; } indent = oldIndent ; }
assert(match(ip,limit,"+node:")); ip += 6 ; ip = skip_ws(ip, limit);

// Compute the child index. char *np = ip ; // Start of number. while (ip < limit && isdigit(*ip)) ++ip ;

if (np == ip || !match(ip, limit, ":")) error("Bad child index in @+node"); else { String s(np, ip-np); n = s . ToInt(); ++ip ; // Skip the ":". }

<< Set status bits in dummy vnode >>

if ( match(ip, limit, ":") ) ++ ip ; else error("Bad attribute field in @+node");

// Set headline. char *hp = ip ;

if ( mEndSentinelComment . IsEmpty() ) ip = skip_to_end_of_line(ip, limit); else { while ( ip < limit && ! is_nl(*ip) && ! match (ip, limit, mEndSentinelComment . c_str() ) ) ++ ip ; } headline = String(hp, ip-hp); headline = headline . TrimRight();

// Set reference if it exists. ip = hp ; ip = skip_ws(ip, limit); char *rp = ip ; if (match(ip, limit, "<<")) { while ( ip < limit && !is_nl(*ip) && !match(ip, limit, ">>")) ++ ip ; if (match(ip, limit, ">>")) ref = String(rp, ip+2-rp); } ip = skip_line(hp, limit);
while ( ip < limit && *ip != ':' && !is_nl(*ip) ) { long index = 0 ;

switch (*ip++) { case 'C' : << set index from the C=nnn, field >> dummy . t() -> setCloneIndex(index); break ; default : break ; // Ignore unknown status bits. } }
@code

if ( *ip == '=' ) { ++ ip ; char *ip1 = ip ; while ( ip < limit && isdigit(*ip) ) ++ ip ; String s(ip1, ip - ip1); index = s.ToInt(); if ( ip < limit && *ip == ',' ) ++ ip ; }
String s( (char *) new_ms -> Memory, new_ms -> Size); String body = child -> bodyString();

if ( child -> isDummy() ) child -> clearDummy(); else if ( child -> isOrphan() ) error("Replacing body text of orphan: " + child -> headString()); else if ( body . Length() > 0 && body != s ) error("Duplicate definition of " + child -> headString());

// At present we _always_ update the body text. // This is reasonable, but maybe not best. child -> t() -> setTnodeText ( s );
String fileName = headline.Trim(); long len = fileName.Length(); if ( len > 5 && fileName.SubString(1,5) == "@file") { fileName = fileName.SubString(6,len-5); if ( fileName.Trim() != mTargetFileName.Trim() ) error("File name in @node sentinel does not match file's name"); } else error ("Missing @file in root @node sentinel");
@ We must make sure that the generated at-others is properly indented. @c

assert(match(ip,limit,"+others")); ip = skip_line(ip, limit); // Skip the sentinel.

// Append the real leading whitespace and at-others. { char *ip2 = skip_ws(linep, limit); String ws(linep, ip2 - linep ); this -> appendToBody(ms, ws); this -> appendToBody(ms, "@others"); } ip = scanText(v, ms, ip, limit, endOthers );
@ 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

assert(match(ip,limit,"<<")); { String line(""); char *ip1 = ip ;

if ( mEndSentinelComment . IsEmpty() ) { // Append the rest of the sentinel line to the text. ip = skip_to_end_of_line(ip, limit); line = String(ip1, ip-ip1); } else { while ( ip < limit && ! is_nl(*ip) && ! match (ip, limit, mEndSentinelComment . c_str() ) ) ++ ip ; line = String(ip1, ip-ip1); } ip = skip_line(ip1, limit); this -> appendToBody(ms, line); }
assert(match(ip,limit,"verbatim")); { // Skip the sentinel. ip = skip_line(ip,limit);

// Append the next line to the text. char *ip1 = skipIndent ( indent, ip, limit ) ; ip = skip_line(ip, limit); String line (ip1, ip-ip1); this -> appendToBody(ms, line); }
assert(match(ip,limit,"@")); { String line(""); char *ip1 = ip ; // The first '@' has already been eaten.

if ( mEndSentinelComment . IsEmpty() ) { // Append the rest of the sentinel line to the text. ip = skip_line(ip, limit); line = String(ip1, ip-ip1); } else { while ( ip < limit && ! is_nl(*ip) && ! match (ip, limit, mEndSentinelComment . c_str() ) ) ++ ip ; line = String(ip1, ip-ip1); line += "\n" ; ip = skip_line(ip1, limit); } this -> appendToBody(ms, line); }
{ char * ip1 = ip ; ip = skip_line(ip, limit); String line (ip1, ip-ip1); error("Unknown sentinel: " + line); }
@ root is an ancestor of v, or root == v. This puts the entire expansion of v's body text enclosed in sentinel lines. @c

void atFile::putBody ( vnode * root, vnode * v, long delta ) { indent += delta ; putOpenSentinels(root, v) ; putBodyPart(v); v -> setVisited(); putCloseSentinels(root, v) ; indent -= delta ; }
@ We generate the body part only if it contains something besides whitespace. The check for at-ignore is made in atFile::write. @c

void atFile::putBodyPart ( vnode * v ) { char *ip = (char *) v -> bodyCString() ; char *limit = ip + v -> bodyCStringLength() ; char *ip2 = skip_ws_and_nl(ip, limit) ; if ( ip2 >= limit ) return ; // adjust limit so there is no trailing whitespace. while ( limit > ip && is_ws( *(limit-1) ) ) --limit ; putSentinel("@+body"); << put code/doc parts and sentinels >> putSentinel("@-body"); }
while (ip < limit) { short kind = directiveKind(ip, limit) ; char *ip1 = ip ; switch ( kind ) { case docDirective : case atDirective : ip = putDoc(ip, limit, kind); break ; case miscDirective : ip = this -> putDirective(ip, limit); break ; case noDirective : case othersDirective : ip = this -> putCodePart(v, ip, limit); break ; case cDirective : case codeDirective : ip = this -> putDirective(ip, limit); ip = this -> putCodePart(v, ip, limit); break ; default : assert (0); } assert (ip1 < ip); // We must make progress. }
@ This method expands a code part, terminated by any at-directive except at-others. It expands references and at-others and outputs @verbatim sentinels as needed. @c

char * atFile::putCodePart ( vnode * v, char * ip, char * limit ) { bool atOthersSeen = FALSE ; // TRUE: at-others has been expanded. while (ip < limit) { long delta = 0 ; // How much indent should change. << handle the start of a line >> << put the line >> } done: return ip ; }
@ The at-others directive is the only directive that is recognized following leading whitespace, so it is just a little tricky to recognize it. @c

<< compute delta, the width of the whitespace >>

short kind1 = directiveKind(ip, limit); short kind2 = directiveKind(skip_ws(ip,limit), limit);

if ( kind1 == othersDirective || kind2 == othersDirective ) { << handle @others >> } else if ( kind1 == noDirective ) { << put @verbatim sentinel if necessary >> } else goto done ;
delta = 0 ; { char * ip2 = ip ; while ( ip2 < limit ) { if ( *ip2 == ' ' ) { ++ delta ; ++ ip2 ; } else if ( *ip2 == '\t' ) { delta += (arg_tab_width - (delta % arg_tab_width)) ; ++ ip2 ; } else break ; } }
// This skips all indent and delta whitespace, so putAtOthers must generate it all.

ip = skip_to_end_of_line(ip, limit); // Bug fix: 7/6/01 if ( atOthersSeen ) writeError( "@others already expanded in: " + v -> headString()); else { atOthersSeen = TRUE ; putAtOthers(v, delta); }
{ String s(mStartSentinelComment); s += "@" ; if (match (ip, limit, s.c_str())) putSentinel("verbatim"); }
putIndent(indent);

while (ip < limit ) { if (*ip == body_newline) { onl(); ip = skip_nl(ip, limit); break ; } else if (*ip == body_ignored_newline) ++ip; // at-others is recognized _only_ at the start of a line. else if (*ip == '<') { << put possible section reference >> } else ochar(*ip++); }
char *end_p = NULL ;

if ( isSectionName ( ip, limit, &end_p ) ) { // Create a reference sentinel. String s(ip, end_p - ip); putSentinel("@" + s);

// Output the expansion. vnode * ref = findReference(v, ip, end_p, limit); if ( ref ) putBody(v, ref, delta); else { String s("undefined section: "); s += String(ip, end_p - ip); s += "\n\treferenced from: "; s += v -> headString(); writeError(s); } ip = end_p ; } else ochar(*ip++); // This is _not_ an error.
@ ip points to the start of a reference in root. We search the descendents of root looking for the matching definition node. There should be exactly one such node (descendents of other definition nodes are not searched). @c

vnode * atFile::findReference ( vnode * root, char * ip, char *end_p, char * limit ) { String pattern(ip, end_p - ip); vnode * next = root -> nodeAfterTree(); vnode * v = root -> firstChild(); while ( v && v != next ) { if ( v -> matchHeadline(pattern) && ! v -> isAtIgnoreNode() ) return v ; v = v -> threadNext(); } return NULL ; }
@ Returns TRUE if v should be included in the expansion of the at-others directive in the body text of v's parent. v will not be included if it is a definition node or if its body text contains another at-others or @ignore directive. @c

bool atFile::inAtOthers (vnode * v) { // Return FALSE if this has been expanded previously. if ( v -> isVisited() ) return FALSE; // Return FALSE if this is a definition node. String h = v -> headString(); char * ip = h . c_str(); char * limit = ip + strlen(ip); ip = skip_ws(ip, limit); if ( isSectionName(ip, limit, NULL) ) return FALSE ; // Return FALSE if v's body contains an @ignore or at-others directive. return ! v -> isAtIgnoreNode() && ! v -> isAtOthersNode() ; }
@ The at-others directive is recognized only at the start of the line. This code must generate all leading whitespace for the opening sentinel. @c

void atFile::putAtOthers ( vnode * v, long delta ) { indent += delta ; putSentinel("@+others"); vnode * child = v -> firstChild(); while ( child ) { if ( this -> inAtOthers( child ) ) putAtOthersChild( child ) ; child = child -> next(); } putSentinel("@-others"); indent -= delta ; }
void atFile::putAtOthersChild ( vnode * v ) { putOpenNodeSentinel(v) ; // Insert the expansion of v. v -> setVisited(); // Make sure it is never expanded again. putBodyPart(v); // Insert expansions of all children. vnode * child = v -> firstChild(); while ( child ) { if ( this -> inAtOthers( child ) ) putAtOthersChild( child ) ; child = child -> next(); } putCloseNodeSentinel(v) ; }
@ This method outputs the directive or reference at ip in a sentinel. @c

char * atFile::putDirective ( char * ip, char * limit ) { assert(ip < limit && *ip == '@'); char *ip1 = ip; ip = skip_to_end_of_line(ip, limit); assert(ip1 < ip); String directive(ip1, ip-ip1); ip = skip_line(ip1, limit); this -> putSentinel ( "@" + directive ) ; return ip ; }
@ This method outputs a doc section terminated by @code or end-of-text. All other interior directives become part of the doc part. @c

char * atFile::putDoc ( char *ip, char *limit, short kind ) { assert(kind == atDirective || kind == docDirective); << skip the @<space> or @doc >> << set limit to the end of the doc part >> String tag = kind == docDirective ? "doc" : "at" ; putSentinel("@+" + tag); ip = putDocPart(ip,limit); putSentinel("@-" + tag); return ip ; }
@ Do not skip whitespace following the @doc or @. It is needed by the read code. @c

if ( kind == docDirective ) ip += 4 ; // Skip the "@doc" else ++ip; // Skip the "@";
@ at-code or the end of body text terminates the doc part. @c

char *ip2 = ip ; while ( ip2 < limit ) { ip2 = skip_line(ip2, limit) ; short kind = directiveKind(ip2, limit) ; if (kind == codeDirective || kind == cDirective) break ; }

limit = ip2 ;
@ This method puts a comment part in comments. @c

char * atFile::putDocPart ( char * ip, char * limit ) { bool single = mEndSentinelComment . IsEmpty() ;

if (!single) { putIndent(indent); os(mStartSentinelComment.c_str()); onl(); } // Put all lines. while (ip < limit) { putIndent(indent); long leading = indent ; if (single) { os(mStartSentinelComment.c_str()); oblank(); leading += mStartSentinelComment . Length() + 1 ; } << copy words, splitting the line if needed >> } if (!single) { // This comment is like a sentinel. onl(); putIndent(indent); os(mEndSentinelComment.c_str()); onl(); // Note: no trailing whitespace. } return ip ; }
@ We remove trailing whitespace from lines that have _not_ been split so that a newline has been inserted by this routine if and only if it is preceded by whitespace. @c

char *line = ip ; // Start of the current line.

while (ip < limit) { char * word = ip; // Start of the current word.

// Skip the next word and trailing whitespace. ip = skip_ws(ip, limit); while (ip < limit && !is_nl(*ip) && !is_ws(*ip)) ++ ip ; ip = skip_ws(ip,limit);

// Output the line if no more is left. if (ip < limit && is_nl(*ip)) break ;

// Split the line before the current word if needed. long len = ip - line ; if (line == word || leading + len < arg_page_width ) word = ip ; // Advance to the next word. else { // Write the line before the current word and insert a newline. String theLine(line, word-line); putString(theLine); onl(); // This line must contain trailing whitespace. line = ip = word ; // Rescan the word on the next line. break ; } }

// Remove trailing whitespace and output the remainder of the line. String theLine(line, ip-line); theLine = theLine . TrimRight(); putString(theLine);

if (ip < limit && is_nl(*ip) ) { ip = skip_nl(ip, limit); onl(); // No an inserted newline and no trailing whitespace. }
@ This is the entry point to the write code. The root vnode should be an @file node. We set the orphan flag if there are problems writing the file to force Commands::write_LEO_file to the tree to the .leo file. @c

void atFile::write ( vnode * root ) { mRoot = root ; mCommands -> endEditing(); // Capture the current headline. << Open files. Set orphan flag and return on errors >> mCommands -> clearAllVisited() ; // Unvisited nodes will be orphans. vnode * next = root -> nodeAfterTree(); updateCloneIndices(root, next); putOpenLeoSentinel("@+leo"); putOpenNodeSentinel(root) ; putBodyPart(root); root -> setVisited(); putCloseNodeSentinel(root) ; putSentinel("@-leo"); delete mOutputStream ; // Close the output file. mOutputStream = NULL ; << Warn about @ignored and orphans >> if ( mErrors > 0 || mRoot -> isOrphan() ) { mRoot -> setOrphan() ; remove ( mOutputFileName.c_str() ) ; // Delete the temp file. << Issue final write error message >> } else { << Replace the target with the temp file if different >> } }
@code

// Set globals from ancestors. this -> scanAllDirectives(root); if ( mErrors > 0 ) { mRoot -> setOrphan(); return ; }

mTargetFileName = root -> atFileNodeName(); << setOrphan and return if mTargetFileName is bad >> << Open a temp file and set mOutputFileName >> mOutputStream = new TFileStream(mOutputFileName, fmOpenWrite); if ( mOutputStream == NULL ) { mRoot -> setOrphan(); return ; }
@code

if ( mTargetFileName . Length() == 0 ) { root -> setOrphan(); return ; }

long index = mTargetFileName.LastDelimiter("\\");

if ( index > 0 ) { String path = mTargetFileName.SubString(1, index - 1); if ( path.Length() > 0 && ! DirectoryExists(path) ) { root -> setOrphan(); return ; } }
@ We create the temporary file where the target file will eventually be placed:

1. In the directory specified in mTargetFileName if it contains a full path. 2. In the directory specified in an @path directive. 3. In the directory specified in the Preferences panel. 4. In the current directory.

scanAllDirectives sets the current directory as specified by @path or the Preferences panel, so we need only check to see if mTargetFileName contains a full path. @c

// Put the current directory path in pathname. char pathname[MAXPATH]; strcpy(pathname, "X:\\"); pathname[0] = 'A' + getdisk(); // replace X with current drive letter int result = getcurdir(0, &pathname[3]); if ( result == -1 ) strcat(pathname, "\\");

// Make sure pathname ends with a backslash. long len = strlen(pathname); if ( len == 0 || pathname[len-1] != '\\') strcat(pathname, "\\");

// scanAllDirectives also checks directory names. if ( ! DirectoryExists ( pathname ) ) { writeError ( String("Directory \"") + pathname + "\" does not exist " ); return ; }

int handle = creattemp(pathname, 0); if (handle == -1 ) { writeError ( String("Can not create \"") + pathname + "\"" ); return ; } else close (handle);

String s ( (char *) pathname); mOutputFileName = s ;
for ( vnode * v = root ; v && v != next ; v = v -> threadNext() ) { if ( ! v -> isVisited() ) { mRoot -> setOrphan(); String s("Orphan node: "); s += v -> headString(); writeError ( s.c_str() ) ; } if ( v -> isAtIgnoreNode() ) { mRoot -> setOrphan(); String s("@ignore node: "); s += v -> headString(); writeError ( s.c_str() ) ; } }
ecnl() ; es("----- Not written: "); es(mTargetFileName.c_str()); enl();
if ( compareFiles ( mOutputFileName, mTargetFileName ) ) { // Just delete the temp file. remove ( mOutputFileName.c_str() ) ; es("Unchanged: "); es(mTargetFileName.c_str() ); enl(); } else { // Replace target file with temp file. if ( FileExists ( mTargetFileName ) ) remove ( mTargetFileName . c_str() ) ; if ( 0 != rename ( mOutputFileName . c_str(), mTargetFileName.c_str() ) ) { writeError("Rename failed: no file created!"); } else { es("Writing: "); es(mTargetFileName.c_str() ); enl(); } }
@ This method scans all vnodes, calling write for every @file node found. v should point to the root of the entire tree. @c

void atFile::writeAll ( vnode * v ) { << Clear all orphan bits >> while ( v ) { if ( v -> isAtIgnoreNode() ) v = v -> nodeAfterTree(); else if ( v -> isAtFileNode() ) { if ( v -> isDirty() ) this -> write(v) ; v = v -> nodeAfterTree(); } else v = v -> threadNext(); } }
@ 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 ( vnode * v2 = v ; v2 ; v2 = v2 -> threadNext() ) v2 -> clearOrphan();
@ These methods write their arguments to the output file.
void atFile::putBlanks ( int n ) { while (n-- > 0) ochar(' '); }
void atFile::putChar (char c) { if ( mOutputStream ) mOutputStream -> Write( &c, 1 ); }
@ This methods puts tabs and spaces corresponding to n spaces, assuming that we are at the start of a line. @c

void atFile::putIndent ( int n ) { if ( arg_tab_width > 1 ) { otabs ( n / arg_tab_width ) ; oblanks ( n % arg_tab_width ) ; } else oblanks ( n ) ; }
void atFile::putInt (int i) { char buf [100]; long n = sprintf(buf, "%d", i); if ( n != EOF ) putString(buf); }
void atFile::putString ( String s ) { if ( mOutputStream ) mOutputStream -> Write( s.c_str(), s.Length() ); }
void atFile::putTabs ( int n ) { while ( n-- > 0 ) ochar('\t'); }
String atFile::nodeSentinelText ( vnode * v ) { String s(""); // A hack: zero indicates the root node so scanText won't create a child. long index = 0 ; if ( v != mRoot && v -> parent() ) index = v -> childIndex() + 1 ; s += String(index); s += ":" ; << append the status-bit characters to s >> s += ":" ; s += v -> headString(); return s ; }
@ The cloneIndex field of the @root node is really the maximum clone index.

We make the @file node the top or current node if the top or current node is a descendent of the root. This minimizes changes to the derived file. @c

long cloneIndex = v -> t() -> cloneIndex() ;

if ( cloneIndex > 0 ) { s += "C=" ; s += String ( cloneIndex ); }

void atFile::putCloseNodeSentinel ( vnode * v ) { String s = this -> nodeSentinelText(v); this -> putSentinel( "@-node:" + s); }
@ root is an ancestor of v, or root == v. We call putCloseSentinel for v up to, but not including, root. @c

void atFile::putCloseSentinels ( vnode * root, vnode * v ) { this -> putCloseNodeSentinel(v); for (;;) { v = v -> parent(); assert(v); // root must be an ancestor of v. if ( v == root ) break ; this -> putCloseNodeSentinel(v); } }
@ This method is the same as putSentinel except we don't put an opening newline and leading whitespace. @c

void atFile::putOpenLeoSentinel ( String text ) { os(mStartSentinelComment); os(text); os(mEndSentinelComment); onl(); // Ends of sentinel. }
@ This method puts an open node sentinel for node v. @c

void atFile::putOpenNodeSentinel ( vnode * v ) { if ( v -> isAtFileNode() && v != mRoot ) writeError("@file not valid in: " + v -> headString() ); else { String s = this -> nodeSentinelText(v); this -> putSentinel( "@+node:" + s); } }
@ root is an ancestor of v, or root == v. We call putOpenNodeSentinel on all the descendents of root which are the ancestors of v. @c

void atFile::putOpenSentinels ( vnode * root, vnode * v ) { vnode * last = root ; while ( last != v ) { // Set node to v or the ancestor of v that is a child of last. vnode * node = v ; while ( node && node -> parent() != last ) node = node -> parent(); assert(node); this -> putOpenNodeSentinel(node); last = node ; } }
@ All sentinels are eventually output by this method.

Sentinels include both the preceding and following newlines. This rule greatly simplies the code and has several important benefits:

1. Callers never have to generate newlines before or after sentinels. Similarly, routines that expand code and doc parts never have to add "extra" newlines. 2. There is no need for a "no-newline" directive. If text follows a section reference, it will appear just after the newline that ends sentinel at the end of the expansion of the reference. If no significant text follows a reference, there will be two newlines following the ending sentinel.

The only exception is that no newline is required before the opening "leo" sentinel. The putLeoSentinel and isLeoSentinel routines handle this minor exception. @c

void atFile::putSentinel ( String text ) { onl(); putIndent(indent); // Start of sentinel. os(mStartSentinelComment); os(text); os(mEndSentinelComment); onl(); // Ends of sentinel. }
@ This method tells what kind of sentinel appears at ip. Typically ip will point to the newline before the actual sentinel, but it is also valid for ip to point directly at the start of a sentinel line. @c

#define test(name,start,end)\ if(match_word(ip,limit,name)) { return plus ? start : end ; }

short atFile::sentinelKind ( char *ip, char *limit ) { if (is_nl(*ip)) ip = skip_nl(ip, limit) ; ip = skip_ws(ip,limit); if (!match(ip, limit, mStartSentinelComment.c_str())) return noSentinel ; ip += mStartSentinelComment . Length() ; if (!match(ip, limit, "@")) return noSentinel ; ++ip ; // Skip the first '@'. if (match(ip, limit, "@")) return startDirective ; else if (match(ip, limit, "@verbatim")) return startVerbatim ; else if (match(ip, limit, "<<")) return startRef ;

// All the following start with + or -. char delim = *ip++ ; if (delim != '+' && delim != '-') return noSentinel ; bool plus = delim == '+' ; test("at", startAt, endAt); test("body", startBody, endBody); test("doc", startDoc, endDoc); test("leo", startLeo, endLeo); test("node", startNode, endNode); test("others", startOthers, endOthers); return noDirective ; }

#undef test
@ This method returns the name of the sentinel for warnings. @c

String atFile::sentinelName ( short sentinelKind ) { switch ( sentinelKind ) { case endAt: return "@-at" ; case endBody: return "@-body" ; case endDoc: return "@-body" ; case endLeo: return "@-leo" ; case endNode: return "@-node" ; case endOthers: return "@-others" ; case noSentinel: return "<no sentinel>" ; case startAt: return "@+at" ; case startBody: return "@+body" ; case startDirective: return "@@" ; case startDoc: return "@+doc" ; case startLeo: return "@+leo" ; case startNode: return "@+node" ; case startOthers: return "@+others" ; case startVerbatim: return "@verbatim" ; default: return "<unknown sentinel!>" ; } }
char * atFile::skipSentinelStart( char * ip, char * limit) { if (is_nl(*ip)) ip = skip_nl(ip, limit) ; ip = skip_ws(ip, limit); assert(match(ip, limit, mStartSentinelComment.c_str())); ip += mStartSentinelComment . Length() ; assert(ip < limit && *ip == '@'); return ++ip ; }
@ Returns TRUE if two files are identical. @c

bool compareFiles ( String path1, String path2 ) { char buf1[1024], buf2[1024]; int f1 = FileOpen(path1, fmOpenRead) ; int f2 = FileOpen(path2, fmOpenRead) ; bool result = f1 > -1 && f2 > -1 ; while (result) { long n1 = FileRead(f1, buf1, 1024) ; long n2 = FileRead(f2, buf2, 1024) ; if (n1 == 0 && n2 == 0) break ; // Files are equal. result = (n1 == n2 && memcmp(buf1, buf2, n1) == 0); } if (f1 > -1) FileClose(f1); if (f2 > -1) FileClose(f2); return result ; }
@ Returns the kind of at-directive or noDirective. @c

short directiveKind ( char * ip, char * limit ) { if ( ip >= limit || *ip != '@' ) return noDirective ; else if ( match_word(ip, limit, "@c") ) return cDirective ; else if ( match_word(ip, limit, "@code") ) return codeDirective ; else if ( match_word(ip, limit, "@doc") ) return docDirective ; else if ( match(ip, limit, "@ ") || match(ip, limit, "@\t") || match(ip, limit, "@\n") ) return atDirective ; else if (match_word(ip, limit, "@others")) return othersDirective ; else return miscDirective ; }
@ Returns TRUE if ip points to the start of a section name. If so, we set end_pp in the caller to the character past the section reference. @c

bool isSectionName ( char *ip, char *limit, char **end_pp ) { if (! match(ip, limit,"<<")) return FALSE ;

// Find the end of the section name. while (ip < limit && !is_nl(*ip) && !match(ip,limit, ">>" ) ) ++ ip ;

if (end_pp && match(ip, limit, ">>")) *end_pp = ip + 2 ;

return match(ip, limit, ">>") ; }
@ This function sets any ancestor @file node to be dirty. @c

void setAncestorAtFileNodeDirty ( vnode * v ) { while ( v ) { if ( v -> isAtFileNode() ) { v -> setDirty(); v -> setDirtyJoined(); } v = v -> parent(); } }
void atFile::appendToBody (TMemoryStream *ms, String s) { assert(ms); if ( s . Length() > 0 ) ms -> Write( s . c_str(), s . Length() ) ; }

void atFile::appendToBody (TMemoryStream *ms, char *start, char *end) { assert(ms); long len = end - start ; if ( len > 0 ) ms -> Write( start, len ) ; }
@ This code scans the node v and all of v's ancestors looking for directives. If found, the corresponding Tangle/Untangle globals are set.

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 x, no @owncolor or @nocolor directives are examined in any ancestor of x.

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

#define test(bit) ((bit & bits) != 0) #define prev(bit) ((bit & old_bits) != 0)

void atFile::scanAllDirectives ( vnode * v ) { assert(v); TLeoForm * form = v -> leoForm() ; long old_bits = 0 ; // One bit for each directive. long val = 0 ; bool path_specified = FALSE ; << Set global arguments to default values >> << Set path from @file node >> for ( ; v ; v = v -> parent() ) { String s; // Must have the same lifetime as ip! char * ip = v -> bodyCString() ; long len = v -> bodyCStringLength() ; char * limit = ip + len ; long bits = is_special_bits ( ip, len, DONT_SET_ROOT_FROM_HEADLINE ) ; << Test for @path >> << Test for @comment or @language >> << Test for @pagewidth and @tabwidth >> old_bits |= bits ; } if ( !path_specified && !prev(path_bits)) { // No path in @file headline and no @path directive. << Set current directory if possible >> } << Set comment Strings from global vars >> }

#undef test #undef prev
#if 0 // not used in atFile::scanAllDirectives. // CWEB flag might be useful. arg_allow_rich_text = default_allow_rich_text ; arg_extended_noweb_flag = default_extended_noweb_flag ; arg_output_doc_flag = default_output_doc_flag ; arg_tangle_batch = default_tangle_batch ; arg_untangle_batch = default_untangle_batch ; arg_use_noweb_flag = default_use_noweb_flag ; arg_use_CWEB_flag = default_use_CWEB_flag ; arg_use_header_flag = default_use_header_flag ; // 6/1/00 tangle_print_bits = verbose_bits ; #endif

arg_page_width = default_page_width ; arg_tab_width = default_tab_width ; arg_present_language = arg_target_language = default_target_language ; set_delims_from_language(arg_present_language);
@ A directory in an @file node over-rides everything else. @c

String s = v -> atFileNodeName() ; String dir = ExtractFileDir ( s ) ; if ( dir.Length() > 0 ) { path_specified = SetCurrentDir ( dir ) ; if ( ! path_specified ) error ( String("Directory \"") + dir + "\" does not exist "); }
if ( prev(comment_bits) || prev(language_bits) ) ; // Do nothing more. else if (test(comment_bits)) { set_root_delims ( at_comment_ptr, len-(at_comment_ptr-ip) ) ; // @comment effectively disables Untangle. arg_present_language = unknown_language ; } else if (test(language_bits)) set_language (ip, len, at_language_ptr, 0 /* issue_error_flag */ ) ;
if (test(path_bits) && !path_specified && !prev(path_bits)) { char * p2 = at_path_ptr + 5 ; // Point past @path ip = skip_to_end_of_line(at_path_ptr, limit); String s(p2, ip - p2 ); s = s . Trim(); << Remove leading and trailing delims if they exist >> s = s . Trim(); if ( s . Length() > 0 ) { path_specified = SetCurrentDir ( s . c_str() ) ; if ( ! path_specified ) error ( "Invalid @path: " + s ) ; } else error ("ignoring empty @path") ; }
if ( s . Length() > 2 && ( s[1] == '"' && s[s.Length()] == '"' || s[1] == '<' && s[s.Length()] == '>' ) ) { s . Delete ( s.Length(), 1 ) ; s . Delete ( 1, 1 ) ; }
if (test(page_width_bits) && !prev(page_width_bits) ) { char * p2 = at_page_width_ptr + 10 ; // Point past @pagewidth val = 0 ; ip = skip_long (p2, limit, & val ); if ( ip == NULL ) { ip = skip_to_end_of_line(p2, limit); String line(at_page_width_ptr, ip - at_page_width_ptr); error ( "Ignoring " + line ) ; } else arg_page_width = val ; }

if (test(tab_width_bits) && !prev(tab_width_bits) ) { char * p2 = at_tab_width_ptr + 9 ; // Point past @tabwidth val = 0 ; ip = skip_long (p2, limit, & val ); if ( ip == NULL ) { ip = skip_to_end_of_line(p2, limit); String line(at_tab_width_ptr, ip - at_tab_width_ptr); error ( "Ignoring " + line ) ; } else arg_tab_width = val ; }
@ This code is executed if no valid path was specified in the @file node or in an @path directive. We try to set the current directory to the following:

1. The Tangle Default Directory specified in the Preferences panel. 2. The mOpenDirectory specified by the Open command. @c

if ( form == NULL ) path_specified = TRUE ; // We are opening. Do nothing.

if ( !path_specified && form -> mDefaultDirectory . Length() > 0 ) { path_specified = SetCurrentDir ( form -> mDefaultDirectory . c_str() ) ; if ( !path_specified ) error( "Invalid Default Tangle Directory: " + form -> mDefaultDirectory ) ; }

if ( !path_specified && form -> mOpenDirectory . Length() > 0 ) { path_specified = SetCurrentDir ( form -> mOpenDirectory . c_str() ) ; if ( !path_specified ) error ( "Directory no longer valid: " + form -> mDefaultDirectory ) ; }

if ( !path_specified ) { // Always issue this error. error ("No directory specified by @file, @path or Preferences."); }
@ We use single-line comments if we have a choice. @c

if ( single_comment_string ) { mStartSentinelComment = single_comment_string ; mEndSentinelComment = "" ; } else { mStartSentinelComment = start_comment_string ; mEndSentinelComment = end_comment_string ; }
void atFile::error ( String message ) { if ( message.Length() > 0 ) { message += "\n" ; put_cstring_to_log_window ( message.c_str() ); } ++ mErrors ; }
@ ip points at the start of a line. Skip past whitespace equivalent to n spaces. @c

char * atFile::skipIndent ( long n, char * ip, char * limit ) { long ws = 0 ; while (ip < limit && ws < n) { if (*ip == '\t') ws += ( arg_tab_width - ws % arg_tab_width ) ; else if (*ip == ' ') ++ ws ; else break ; ++ ip ; } return ip ; }
@ Update the cloneIndex field of each vnode of root's subtree. The maximum clone index is stored in the cloneIndex field of root (root should not be cloned). next points to the node after the tree. @c

void atFile::updateCloneIndices ( vnode * root, vnode * next ) { if ( root -> isCloned() ) { error ("Ignoring clone mark for " + root -> headString()); root -> t() -> setCloneIndex(0); }

long maxIndex = root -> t() -> cloneIndex() ;

for ( vnode * v = root ; v && v != next ; v = v -> threadNext() ) if ( v -> isCloned() && v -> t() -> cloneIndex() == 0 ) v -> t() -> setCloneIndex ( ++ maxIndex ) ;

root -> t() -> setCloneIndex(maxIndex) ; }
void atFile::writeError ( String message ) { if ( mErrors == 0 ) { es("----- Errors in: "); es(mTargetFileName.c_str() ); enl(); } error ( message ) ; mRoot -> setOrphan(); }
#pragma option -w-par // suppress warning: "Parameter `param' not used."

<< File commands includes >> << Declare the fileInfo class >> << Declare the EBadLeoFile class >> << Declare File commands private functions >>

@others
#include <vcl.h> #pragma hdrstop

#include "args.h" #include "atFile.h" #include "Clipbrd.hpp" #include "Commands.h" #include "CloneUnit.h" #include <Filectrl.hpp> // For DirectoryExists. #include "FindUnit.h" // for find globals. #include "LeoWindow.h" #include "MainUnit.h" // For alert(). #include "Prefs.h" // Alters Prefs form. #include "Sherlock.h" // For Sherlock.cpp (defines SherlockForm). #include "vnode.h" #include "tnode.h" #include "Utils.h"

#include <ctype.h> #include <stdio.h> #include <stdlib.h>

#include "Leo.h"
static String convertToNoweb ( vnode * v ) ; static char * convertCodePartToNoweb( vnode *v, char *p, char *limit, String& result) ; static char * convertDocPartToNoweb( char *p, char *limit, String& outerResult) ; static char * copyPart( char *p, char *limit, String& result) ;
class EBadLeoFile {} ;
void Commands::deleteTagList( void ) { while ( mTagList ) { tag_list * next = mTagList -> next ; delete mTagList ; mTagList = next ; } }
vnode * Commands::getVtag ( long vref ) { for ( tag_list * p = mTagList ; p ; p = p -> next ) if ( p -> vref == vref ) return p -> v ;

return NULL ; }

long Commands::getVtag ( vnode * v ) { for ( tag_list * p = mTagList ; p ; p = p -> next ) if ( p -> v == v ) return p -> vref ;

return NULL ; }
void Commands::newVtag ( long vtag, vnode * v ) { // Allocate the node. tag_list * p = new tag_list(); p -> v = v ; p -> vref = vtag ;

// Link the node into the list. p -> next = mTagList ; mTagList = p ; }
void Commands::exportMoreText ( void ) { vnode * v = this -> currentVnode(); if ( v == NULL ) return ; TClipboard * clipboard = Clipboard(); if ( clipboard == NULL ) return ;

String s = v -> convertTreeToString() ; clipboard -> Open(); clipboard -> Clear(); clipboard -> SetTextBuf( s . c_str() ); clipboard -> Close(); }
void Commands::flattenOutline ( TStrings * files ) { vnode * v = this -> currentVnode() ; if ( v == NULL ) return ; long count = files -> Count ; if ( count != 1 ) return ;

String fileName = files -> Strings [ 0 ] ; vnode * after = v -> nodeAfterTree() ; String s ; String body ; String newline ("\n") ; long firstLevel = v -> level();

try { int fileHandle = FileCreate( fileName) ;

#if 0 // Elegant, but uses lots of memory. s = v -> convertTreeToString() ; FileWrite(fileHandle, s . c_str(), s . Length() ) ; #else // Redundant, but uses much less memory. while ( v && v != after ) { s = v -> moreHead ( firstLevel ) ; s += newline ; body = v -> moreBody (); // Insert escapes. if ( body . Length() > 0 ) { s += body ; s += newline ; } FileWrite(fileHandle, s . c_str(), s . Length() ) ; v = v -> threadNext() ; } #endif FileClose(fileHandle) ; } catch(...) { alert("File error while flattening the outline") ; } }
void Commands::flattenOutlineWithFilename ( String fileName ) { TStringList * list = new TStringList(); list -> Add(fileName); flattenOutline(list); delete list ; }
void Commands::outlineToNoweb( TStrings * files ) { vnode * v = this -> currentVnode() ; if ( v == NULL ) return ; long count = files -> Count ; if ( count != 1 ) return ;

String fileName = files -> Strings [ 0 ] ; vnode * after = v -> nodeAfterTree() ;

try { int fileHandle = FileCreate( fileName) ;

while ( v && v != after ) { String s = convertToNoweb(v); if ( s . Length() > 0 ) { // Don't rely on s.Length() when writing! char * cs = s.c_str(); long len = strlen(cs); if ( len > 0 ) FileWrite(fileHandle, cs, len ) ; if ( len > 0 && cs[len-1] != '\n' ) FileWrite(fileHandle, "\n", 1 ) ; } v = v -> threadNext() ; }

FileClose(fileHandle) ; } catch(...) { alert("File error in Outline To noweb command") ; } }
@ This code converts a vnode to noweb text as follows:

Convert @root to < < * > > Convert @doc to @ Convert @code to < < name > > =, assuming the headline contains < < name > > Ignore other directives Format parts so they fit in pagewidth columns. Output code parts as is. @c

static String convertToNoweb ( vnode * v ) { String result (""); if ( v == NULL ) return result ; char * p = v -> bodyCString(); char * limit = p + v -> bodyCStringLength() ; char * p2 = skip_ws_and_nl(p, limit); if ( p2 == limit ) return result ;

while ( p < limit) { p = skip_ws_and_nl(p, limit); if ( p >= limit ) break ; else if ( p + 3 < limit && strn_eq(p, "@doc", 4)) p = convertDocPartToNoweb(p + 4, limit, result); else if ( p + 4 < limit && strn_eq(p, "@code", 5)) p = convertCodePartToNoweb(v, p, limit, result); else if ( p + 4 < limit && strn_eq(p, "@root", 5)) p = convertCodePartToNoweb(v, p, limit, result); else if ( p + 1 < limit && strn_eq(p, "<<", 2)) p = convertCodePartToNoweb(v, p, limit, result); else if ( p + 1 < limit && strn_eq(p, "@ ", 2)) p = convertDocPartToNoweb(p + 2, limit, result); else if ( *p == '@' ) p = skip_line(p, limit); // Ignore all other directives. else // Assume we are in a doc part. p = convertDocPartToNoweb(p, limit, result); }

#if 0 // this is not the problem TReplaceFlags flags ; flags << rfReplaceAll ; result = StringReplace( result, "\r", "\n", flags ) ; #endif

return result ; }
@ The code part should start either with @code or @root or << section name >>= @c

static char * convertCodePartToNoweb( vnode *v, char *p, char *limit, String& result) { if ( p >= limit ) return p ; << Output the section reference >> p = copyPart(p, limit, result); return p ; }
if ( p + 4 < limit && strn_eq(p, "@code", 5)) { p = skip_line(p, limit); char * p1 = NULL, *p2 = NULL ; << point p1 and p2 at section name in v's headline >> if ( p1 && p2 ) { String headline(p1, p2-p1); result += "\n" ; result += headline ; result += "=" ; } else { result += "\n\n<< *** " ; result += "no section name for @code ***>>" ; } } else if ( p + 4 < limit && strn_eq(p, "@root", 5)) { String name("*"); // Default name for empty file names. p += 5 ; p = skip_ws(p, limit); char * p2 = p ; p = skip_line(p, limit); << Set name to the file name p2..p >> result += "\n\n<<" ; result += name ; result += ">>=" ; } else if ( p + 1 < limit && strn_eq(p, "<<", 2)) { // copy the line to the output. char * p2 = skip_line(p, limit); while ( p <= p2 ) result += *p++ ; }
if ( p2 < p ) { char delim = ' '; if ( *p2 == '<' ) { ++ p2 ; delim = '>' ; } if ( *p2 == '"' ) { ++ p2 ; delim = '"' ; } char * p3 = p2 ; while ( p2 < p && *p2 != delim && ! is_nl(*p2) ) ++ p2 ; if ( p3 < p2 ) { name = String(p3, p2-p3); name = name.Trim(); if ( name . Length() == 0 ) name = "*" ; } }
char * hp = v -> headCString(); char * hlim = hp + v -> headCStringLength();

hp = skip_ws (hp, hlim);

if ( hp + 2 < hlim && strn_eq(hp, "<<", 2)) { p1 = hp ; hp += 2 ; while ( hp + 2 <= hlim ) { if ( strn_eq(hp, ">>", 2) ) { p2 = hp + 2 ; break ; } else ++ hp ; } }
static char * convertDocPartToNoweb( char *p, char *limit, String& outerResult) { String result("") ; // Ignore anything following @space or @doc. if ( p + 3 < limit && strn_eq(p, "@doc", 4) || p + 1 < limit && strn_eq(p, "@ ", 2) ) p = skip_line(p, limit);

p = skip_ws_and_nl(p, limit); if ( p >= limit ) return p ; p = copyPart(p, limit, result); if ( result . Length() > 0 ) { // We could break long lines in result here. outerResult += "@ \n" ; outerResult += result ; } return p ; }
@ This code copies characters to result until the end of the present section is seen. @c

static char * copyPart( char *p, char *limit, String& result) { while ( p < limit) { // We should be at the start of a line here. char * p2 = p ; p = skip_nl(p, limit); p = skip_ws(p, limit);

if ( p + 3 < limit && strn_eq(p, "@doc", 4)) break ; else if ( p + 4 < limit && strn_eq(p, "@code", 5)) break ; else if ( p + 4 < limit && strn_eq(p, "@root", 5)) break ; else if ( p + 1 < limit && strn_eq(p, "<<", 2)) { << return p if this is a section definition >> } else if ( p + 1 < limit && strn_eq(p, "@ ", 2)) break ; else if ( p + 1 < limit && strn_eq(p, "@@", 2)) ; // Will be handled below. else if ( *p == '@' ) { p = skip_line(p, limit); // Ignore all other directives. continue ; }

// Copy the entire line. p = p2 ; // Copy leading whitespace. p2 = skip_line(p, limit); while ( p <= p2 ) result += *p++ ; }

return p ; }
// This code leaves p and the outer p2 unchanged. { char * p1 = p ; char * p2 = skip_line (p, limit);

while ( p1 < p2 ) if ( p1 + 3 <= p2 && strn_eq(p1, ">>=", 3) ) return p ; else ++ p1 ; }
@ Almost all the time spent in this command is spent here. @c

vnode * Commands::convertMoreStringsToOutlineAfter ( TStrings * strings, vnode * firstVnode ) { if ( ! this -> stringsAreValidMoreFile ( strings ) ) return NULL ;

<< Declare the vars for convertMoreStringsToOutlineAfter >> this -> BeginUpdate(); while ( index < count ) { s = strings -> Strings [ index ] ; cs = s . c_str() ; level = this -> moreHeadlineLevel ( cs, &newFlag ) - firstLevel ;

if ( level >= 0 ) { << Link a new vnode v into the outline >> << Set the headline string, skipping over the leader >> << Count the number of following body lines >> << Add the lines to the body text of v >> v -> setDirty(); // Set the icon and the dirty bit last. } else ++ index ; } if ( theRoot ) this -> setChanged ( TRUE ) ; this -> EndUpdate();

return theRoot ; }
vnode * theRoot = NULL ; vnode * lastVnode = NULL ; vnode * v = NULL ; // The new vnode. long index = 0 ; long lastLevel = -1 ; bool junkFlag = 0 ; bool newFlag = 0 ; long bodyLines = 0; long firstLevel = 0 ; long level = 0 ; String s(""); char * cs = NULL ;

long count = strings -> Count ; if ( count > 0 ) { s = strings -> Strings [ 0 ] ; cs = s . c_str(); firstLevel = this -> moreHeadlineLevel ( cs, &junkFlag ); }
assert(level >= 0 );

if ( lastVnode == NULL ) theRoot = v = firstVnode -> insertAfter ( ) ; else if ( level == lastLevel ) v = lastVnode -> insertAfter ( ) ; else if ( level == lastLevel + 1 ) v = lastVnode -> insertAsNthChild ( 0 ) ; else { assert ( level < lastLevel ) ;

while ( level < lastLevel ) { -- lastLevel ; lastVnode = lastVnode -> parent(); assert(lastVnode); assert ( lastLevel >= 0 ) ; } v = lastVnode -> insertAfter ( ) ; }

lastVnode = v ; lastLevel = level ;
{ char * cString = cs ;

while ( *cString == '\t' ) ++ cString ;

if ( ( *cString == '+' || *cString == '-' ) && *(cString+1) == ' ' ) cString += 2 ;

v -> treeNode() -> Text = String ( cString ) ; }
bodyLines = 0 ; ++ index ; // Skip the headline.

while ( index < count ) { s = strings -> Strings [ index ] ; cs = s . c_str(); level = this -> moreHeadlineLevel ( cs, &newFlag ) - firstLevel ; if ( level >= 0 ) break ;

// Remove first backslash of the body line. if ( s . Length() > 0 && s [ 1 ] == '\\' ) strings -> Strings [ index ] = s . Delete ( 1, 1 ) ;

++ bodyLines ; ++ index ; }
if ( bodyLines > 0 ) { String body ( "" ) ;

for ( long n = index - bodyLines ; n < index ; ++ n ) { body += strings -> Strings [ n ] ; if ( n != index - 1 ) body += "\n" ; }

v -> t() -> setTnodeText ( body ) ; }
@ On entry, files contains at most one file to convert. @c

void Commands::importMoreText ( TStrings * files ) { vnode * current = this -> currentVnode() ; if ( current == NULL ) return ; if ( files -> Count < 1 ) return ;

String fileName = files -> Strings [ 0 ] ; TStrings * strings = new TStringList () ; << Read the file into strings >> // Convert the string to an outline and insert it after the current node. vnode * newVnode = this -> convertMoreStringsToOutlineAfter ( strings, current ) ; delete strings ; strings = NULL ;

if ( newVnode ) { this -> endEditing () ; this -> validateOutline () ; this -> editVnode ( newVnode ) ; this -> setChanged ( TRUE ) ; } else { String s = fileName + " is not a valid MORE file." ; alert ( s . c_str() ) ; } }
@ This code is _very_ fast. @c

try { strings -> LoadFromFile ( fileName ) ; } catch ( ... ) { String s ( "Not enough memory to convert: ") ; s += fileName ; alert ( s . c_str() ); return ; }
@ return the headline level of s, or -1 if the string is not a MORE headline. @c

long Commands::moreHeadlineLevel ( char * s, bool * plusFlag ) { long level = 0 ;

while ( *s && *s == '\t') { ++level ; ++s ; }

if ( plusFlag ) *plusFlag = ( *s == '+' ) ;

if ( ( *s == '+' || *s == '-' ) && * (s+1) == ' ' ) return level ; else return -1 ; }
bool Commands::stringsAreValidMoreFile ( TStrings * strings ) { long count = strings -> Count ; if ( count < 1 ) return FALSE ;

bool plusFlag = 0 ; char *cs = strings -> Strings [0] . c_str() ; long level1 = this -> moreHeadlineLevel ( cs, &plusFlag ) ; if ( level1 == -1 ) return FALSE ; long lastLevel = level1 ;

// Check the level of all headlines. for ( long i = 1 ; i < count ; ++ i ) { bool newFlag = 0 ; cs = strings -> Strings [ i ] . c_str() ; long level = this -> moreHeadlineLevel ( cs, &newFlag ) ;

if ( level > 0 ) { if ( level < level1 || level > lastLevel + 1 ) return FALSE ; // improper level. else if ( level > lastLevel && plusFlag == FALSE ) return FALSE ; // parent of this node has no children. else if ( level == lastLevel && plusFlag == TRUE ) return FALSE ; // last node has missing child. else { lastLevel = level ; plusFlag = newFlag ; } } }

return TRUE ; }
@ This class buffers all information from an outline while a file is being read. The setMaxTnodeIndex method creates the mTnodes array used to associate tnodes with tnode indices. The createVnode method creates a vnode and also creates its tnode if it has not already been created. The finishOutline completes the construction of the outline.

Vnode tags are not part of vnodes: they are created by this class and destroyed when this class is destroyed. @c

class fileInfo { private: long mTnodesSeen ; long mVnodesSeen ; long mMaxTnodeIndex ; long mNumberOfTnodes ; tnode ** mTnodes ; TLeoForm * mLeoForm ; Commands * mCommands ; vnode * mRootVnode ;

public: // Data. long mFileFormatNumber ; long mCloneWindowsSeen ; long mNumberOfCloneWindows ; vnode * mCurrentVnode ; double mRatio ;

// Birth & death. fileInfo ( TLeoForm * leoForm, Commands * commands ) ; ~fileInfo ( void ) ;

// Methods. vnode * createVnode ( vnode * parent, vnode * next, String headline, short attrib, long tref, long tag, String xfile, long xindex ) ; void finishOutline ( void ) ; vnode * finishPaste ( void ) ; tnode * getT ( long index ) ; void newCloneWindow ( long vref, long top, long left, long height, long width ) ; tnode * newTnode ( long index ) ; void setMaxTnodeIndex ( long n ) ; void setNumberOfTnodes ( long n ) ; };
fileInfo::fileInfo ( TLeoForm * leoForm, Commands * commands ) { mLeoForm = leoForm ; mCommands = commands ; mFileFormatNumber = 0 ; mCloneWindowsSeen = mTnodesSeen = 0 ; mMaxTnodeIndex = 0 ; mRatio = 0.5 ; mNumberOfCloneWindows = mNumberOfTnodes = 0 ; mTnodes = NULL ; mRootVnode = NULL ; mCurrentVnode = NULL ; }

fileInfo::~fileInfo ( void ) { delete [] mTnodes ; mCommands -> deleteTagList(); }
vnode * fileInfo::createVnode ( vnode * parent, vnode * back, String headline, short attrib, long tref, long vtag, String xfile, long xindex ) { vnode * v = NULL ; tnode * t = NULL ;

// No tnode is specified in the file for vnodes with empty body text. // 8/29/99: shared tnodes are placed in the file even if empty. if ( tref == 0 ) t = new tnode(); else { // Bug fix: 8/26/99 t = this -> getT ( tref ) ; if ( t == NULL ) t = this -> newTnode ( tref ) ; }

if ( back ) // create v after back. v = back -> insertAfter ( t ) ; else if ( parent ) // create v as the parent's first child. v = parent -> insertAsNthChild ( 0, t ) ; else { << Create a root vnode >> } v -> initHeadString ( headline) ; v -> initStatus ( attrib ) ;

#ifdef V_XCLONES v -> setXCloneName ( xfile ) ; v -> setXCloneIndex ( xindex ) ; #endif

if ( vtag ) mCommands -> newVtag ( vtag, v ) ; return v ; }
v = mCommands -> rootVnode(); assert(v); v -> setT(t) ; // deletes previous root tnode. mRootVnode = v ;
void fileInfo::finishOutline ( void ) { << Set attributes and icons for all nodes >> << Create join lists of all vnodes >> << Set the current node >> }
this -> mCommands -> BeginUpdate();

for ( vnode * v = mRootVnode ; v ; v = v -> threadNext() ) { if ( v -> isExpandedBitSet() ) v -> treeNode() -> Expand ( FALSE ) ; else v -> treeNode() -> Collapse ( FALSE ) ;

v -> clearDirty () ; // Calls setIcon(); }

this -> mCommands -> EndUpdate();
@ We can create all join lists in a first pass through all vnodes using the mJoinHead field in each tnode. The second pass circularizes each list. @c

for ( vnode * v = mRootVnode ; v ; v = v -> threadNext() ) { tnode * t = v -> t(); // Put v at the head of t's list of joined vnodes. v -> setJoinList ( t -> joinHead() ) ; t -> setJoinHead ( v ) ; }

// Circularize each non-empty list. for ( vnode * v = mRootVnode ; v ; v = v -> threadNext() ) { tnode * t = v -> t(); vnode * head = t -> joinHead() ; if ( head == NULL ) continue ;

// Make sure we don't handle this list again. t -> setJoinHead ( NULL ) ;

// Clear the join list if it has only one member. if ( head == v && v -> joinList() == NULL ) { v -> setJoinList(NULL); continue; }

// Point last at the last vnode of the list. vnode * last = head ; while ( last && last -> joinList() ) last = last -> joinList() ; assert(last);

// Link last to head. last -> setJoinList(head); }
@ Initializing the current vnode is tricky. We must not "update" the old current node, namely the new root.

Leo2: we do not set the current node here, because it might be a dummy node. We must wait until we have a complete node so that the event handler will set the body text properly. @c

if ( mCurrentVnode == NULL ) mCurrentVnode = mRootVnode ;

// Leo2: Just indicate what the current node will be. mCommands -> setCurrentVnode ( mCurrentVnode ) ;

#if 0 // Leo2: this must be done later. mCommands -> setCurrentVnode ( NULL ) ; mCommands -> selectVnode ( mCurrentVnode ) ; #endif
@ This method finishes pasting the outline from the clipboard. @c

vnode * fileInfo::finishPaste ( void ) { mRootVnode = mCommands -> rootVnode(); if ( mCurrentVnode == NULL ) mCurrentVnode = mRootVnode ; vnode * after = mCurrentVnode -> nodeAfterTree(); << Check number of created tnodes >> << Set attributes and icons for all pasted nodes >> // Warning: this will only join pasted clones. #if 1 // Don't allow clones in pasted outlines. << Create join lists of all pasted vnodes >> << Recompute clone bits for pasted vnodes >> #endif mCommands -> compactFileIndices ( ) ; mCommands -> selectVnode ( mCurrentVnode ) ; return mCurrentVnode ; }
if ( mTnodesSeen != mNumberOfTnodes ) { String s("Bad tnode count. Seen: ") ; s += mTnodesSeen ; s += ", Expected: " ; s += mNumberOfTnodes ; alert( s . c_str() ); }
for ( vnode * v = mCurrentVnode ; v && v != after ; v = v -> threadNext() ) { if ( v -> isExpandedBitSet() ) v -> treeNode() -> Expand ( FALSE ) ; else v -> treeNode() -> Collapse ( FALSE ) ;

#if 0 // done later. v -> clearDirty () ; // Calls setIcon(); #endif }
@ We can create all join lists in a first pass through all vnodes using the mJoinHead field in each tnode. The second pass circularizes each list. @c

for ( vnode * v = mCurrentVnode ; v && v != after ; v = v -> threadNext() ) { tnode * t = v -> t(); // Put v at the head of t's list of joined vnodes. v -> setJoinList ( t -> joinHead() ) ; t -> setJoinHead ( v ) ; }

// Circularize each non-empty list. for ( vnode * v = mCurrentVnode ; v && v != after ; v = v -> threadNext() ) { tnode * t = v -> t(); vnode * head = t -> joinHead() ; if ( head == NULL ) continue ;

// Make sure we don't handle this list again. t -> setJoinHead ( NULL ) ;

// Clear the join list if it has only one member. if ( head == v && v -> joinList() == NULL ) { v -> setJoinList(NULL); continue; }

// Point last at the last vnode of the list. vnode * last = head ; while ( last && last -> joinList() ) last = last -> joinList() ; assert(last);

// Link last to head. last -> setJoinList(head); }
@ This must be done after the join lists have been created. The saved clone bit is unreliable for pasted nodes. @c

for ( vnode * v = mCurrentVnode ; v && v != after ; v = v -> threadNext() ) { v -> initClonedBit ( v -> shouldBeClone() ) ; v -> clearDirty () ; // Calls setIcon(); }
@ The entry for tnode i is stored at mTnodes [ i - 1 ]. An index of 0 represents NULL. @c

tnode * fileInfo::getT ( long index ) { return ( index < 1 || index > mMaxTnodeIndex ) ? NULL : mTnodes [ index - 1 ] ; }
void fileInfo::newCloneWindow ( long vref, long top, long left, long height, long width ) { #if USE_CLONE_WINDOW if ( ++ mCloneWindowsSeen > mNumberOfCloneWindows ) { String s("Unexpected number of clone windows. ") ; alert ( s . c_str() ) ; }

vnode * v = mCommands -> getVtag ( vref ) ; if ( v ) { // Create a clone window. assert (mLeoForm ) ; TCloneForm * form = new TCloneForm ( mLeoForm, v ) ; form -> SetBounds(left, top, width, height) ; form -> Show(); form -> SendToBack(); } else { String s("Missing clone tag: "); s += vref ; alert ( s . c_str() ) ; } #endif }
tnode * fileInfo::newTnode ( long index ) { if ( index <= 0 || index > mMaxTnodeIndex || mTnodes [ index - 1 ] != NULL ) { String s("bad tnode index: "); s += index ; s += ". Using empty text." ; // alert ( s . c_str() ) ; es(s.c_str()); enl(); return new tnode(); } else { ++mTnodesSeen ; tnode * t = new tnode () ; mTnodes [ index - 1 ] = t ; t -> setFileIndex ( index ) ; return t ; } }
void fileInfo::setMaxTnodeIndex ( long n ) { mMaxTnodeIndex = n ; mCommands -> mMaxTnodeIndex = n ;

// Create the array of pointers to tnodes. mTnodes = (tnode **) new char [ sizeof (tnode *) * (n+1) ] ;

for ( long i = 0 ; i <= n ; ++ i ) mTnodes [i] = NULL ; }
void fileInfo::setNumberOfTnodes ( long n ) { mNumberOfTnodes = n ; }
bool Commands::getBool ( void ) { skipWs(); char c = *mLinePtr++ ;

if ( c == '0' ) return FALSE ; else if ( c == '1' ) return TRUE ; else throw EBadLeoFile() ; }

double Commands::getDouble ( void ) { skipWs();

char *end = NULL; double val = strtod( mLinePtr, &end ); if ( end == NULL || end == mLinePtr || end > mLineLimit ) throw EBadLeoFile(); mLinePtr = end ; return val ; }

void Commands::getDoubleQuote ( void ) { getTag( "\"" ); }

long Commands::getIndex ( void ) { long val = getLong(); if ( val < 0 ) throw EBadLeoFile() ; return val ; }

long Commands::getLong ( void ) { skipWs();

bool neg = *mLinePtr == '-'; if ( neg ) ++mLinePtr ;

// Count the number of numerals. long len = 0 ; while ( mLinePtr + len < mLineLimit && isdigit( mLinePtr[len] ) ) ++len ; if ( len < 1 ) throw EBadLeoFile() ;

// Compute the value. long val = 0 ; for ( long i = 0 ; i < len ; ++i ) { val *= 10 ; val += ( mLinePtr[i] - '0' ) ; }

mLinePtr += len ; return neg ? - val : val ; }

void Commands::getTag ( char * tag ) { if ( ! matchTag ( tag ) ) throw EBadLeoFile() ; }

bool Commands::matchTag ( char * tag ) { long len = strlen ( tag ) ;

skipWsAndNl(); bool result = ( mLinePtr + len <= mLineLimit && strncmp( mLinePtr, tag, len ) == 0 ) ; if ( result ) mLinePtr += len ;

return result ; }

bool Commands::matchChar ( char c ) { skipWs(); if (*mLinePtr == c) { ++mLinePtr ; return TRUE ; } else { return FALSE ; } }
void Commands::getClipboardHeader ( fileInfo * info ) { // Set defaults. bool maxIndexSeen = FALSE ; bool numberSeen = FALSE ; info -> mFileFormatNumber = 1 ; info -> mNumberOfCloneWindows = 0 ;

getTag("<leo_header");

// New in version 1.7: fields may appear in any order. for (;;) { if ( matchTag("file_format=\"") ) { info -> mFileFormatNumber = getLong(); getDoubleQuote(); } else if ( matchTag("tnodes=\"") ) { numberSeen = TRUE ; info -> setNumberOfTnodes ( getLong() ) ; getDoubleQuote(); } else if ( matchTag("max_tnode_index=\"") ) { maxIndexSeen = TRUE ; info -> setMaxTnodeIndex ( getLong() ) ; getDoubleQuote(); } else { getTag("/>"); break ; } }

if ( ! numberSeen ) info -> setNumberOfTnodes ( 0 ) ; if ( ! maxIndexSeen ) info -> setMaxTnodeIndex ( 0 ) ; }
void Commands::getCloneWindows ( fileInfo * info ) { // The clone_windows tag is optional. if ( ! matchTag("<clone_windows>") ) return ;

long vtag, top, left, height, width ;

while ( matchTag("<clone_window vtag=\"V") ) { vtag = getLong(); getDoubleQuote(); getTag(">"); getTag("<global_window_position"); getPosition ( &top, &left, &height, &width ) ; getTag("/>"); getTag("</clone_window>"); info -> newCloneWindow ( vtag, top, left, height, width ); } getTag("</clone_windows>"); }
String Commands::getEscapedString ( void ) { if ( mTextBuf == NULL ) { << allocate mTextBuf >> } mTextPtr = mTextBuf ;

// Fill mTextBuf up to the next < character. for ( ;; ) { if ( mTextPtr + 1 >= mTextLimit ) { << increase the size of mTextBuf >> } if ( mLinePtr >= mLineLimit ) if ( ! this -> readLine() ) break ; // 11/15/00 if ( mLinePtr >= mLineLimit || *mLinePtr == '<' ) break ; *mTextPtr++ = *mLinePtr++ ; }

assert(mTextPtr < mTextLimit ); *mTextPtr = '\0' ;

return this -> xmlUnescape ( mTextBuf ) ; }
mTextBufSize = 1000 ; mTextBuf = new char [ mTextBufSize ] ; mTextPtr = mTextBuf ; mTextLimit = mTextBuf + mTextBufSize ;
{ long offset = mTextPtr - mTextBuf ; char * oldBuf = mTextBuf ;

// Allocate and fill the new buffer. long size = mTextBufSize + 1000 ; mTextBuf = (char *) new char [ size ] ; assert(mTextBuf); memset(mTextBuf, 0 , size );

if ( oldBuf ) memcpy( mTextBuf, oldBuf, mTextBufSize ) ;

// Set the new buffer values. mTextBufSize += 1000 ; mTextLimit = mTextBuf + mTextBufSize ; mTextPtr = mTextBuf + offset ;

if ( oldBuf ) delete [] oldBuf ; }
#define match_flag(a, b)\ if ( matchTag(b) ) { getTag("=\""); a=getBool(); getDoubleQuote(); }

void Commands::getFindPanelSettings ( void ) { << Set defaults of all flags >> getTag("<find_panel_settings"); for (;;) { match_flag ( batch_flag, "batch" ) else match_flag ( wrap_flag, "wrap" ) else match_flag ( whole_word_flag, "whole_word" ) else match_flag ( ignore_case_flag, "ignore_case" ) else match_flag ( pattern_match_flag, "pattern_match" ) else match_flag ( search_headline_flag, "search_headline" ) else match_flag ( search_body_flag, "search_body" ) else match_flag ( suboutline_only_flag, "suboutline_only" ) else match_flag ( mark_changes_flag, "mark_changes" ) else match_flag ( mark_finds_flag, "mark_finds" ) else match_flag ( reverse_flag, "reverse" ) else break; } getTag(">");

if ( matchTag("<find_string>") ) { find_text = getEscapedString(); getTag("</find_string>"); } else { getTag("<find_string/>"); find_text = "" ; }

if ( matchTag("<change_string>") ) { change_text = getEscapedString(); getTag("</change_string>"); } else { getTag("<change_string/>"); change_text = "" ; } getTag("</find_panel_settings>");

// Update the settings immediately. if ( FindForm ) FindForm -> set_check_boxes(); }

#undef match_flag
batch_flag = FALSE ; wrap_flag = FALSE ; whole_word_flag = FALSE ; ignore_case_flag = FALSE ; pattern_match_flag = FALSE ; search_headline_flag = FALSE ; search_body_flag = FALSE ; suboutline_only_flag = FALSE ; mark_changes_flag = FALSE ; mark_finds_flag = FALSE ; reverse_flag = FALSE ;
void Commands::getGlobals ( fileInfo * info ) { long top, left, height, width ;

getTag("<globals") ; getTag("body_outline_ratio=\""); info -> mRatio = getDouble(); getDoubleQuote(); getTag(">");

getTag("<global_window_position"); getPosition( &top, &left, &height, &width ) ;

<< set the window position >>

getTag("/>");

getTag("<global_log_window_position"); getPosition( &top, &left, &height, &width ) ;

<< set the log window position >>

getTag("/>");

getTag("</globals>") ; }
assert ( mLeoForm ) ; mLeoForm -> SetBounds(left, top, width, height) ;
if ( SherlockForm ) SherlockForm -> SetBounds(left, top, width, height) ;
double Commands::getLeoFile ( TLeoForm * leoForm ) { fileInfo * info = new fileInfo( leoForm, this ); try { readLine(); getTag(prolog_string); getTag("<leo_file>"); getLeoHeader ( info ); getGlobals ( info ); getPrefs ( info ); getFindPanelSettings (); getVnodes ( info ); getTnodes ( info ); getCloneWindows ( info ); getTag ("</leo_file>"); info -> finishOutline(); } catch ( const EBadLeoFile& err ) { String s = *mFileName + String(" is not a valid Leo file"); alert( s . c_str() ); } catch ( ... ) { String s = String("error reading ") + *mFileName ; alert( s . c_str() ); } this -> deleteTagList(); double ratio = info -> mRatio ; delete info ;

// Leo2: read all @file nodes and reset dummy & orphan bits. atFile * at = new atFile( this ); at -> readAll ( this -> rootVnode() ) ; delete at ;

// Leo2: set the current node only after all nodes have been read. if ( mCurrentVnode ) this -> selectVnode ( mCurrentVnode ) ;

return ratio ; }
void Commands::getLeoHeader ( fileInfo * info ) { // Set defaults. bool maxIndexSeen = FALSE ; bool numberSeen = FALSE ; info -> mFileFormatNumber = 1 ; info -> mNumberOfCloneWindows = 0 ;

getTag("<leo_header");

// New in version 1.7: attributes may appear in any order. for (;;) { if ( matchTag("file_format=\"") ) { info -> mFileFormatNumber = getLong(); getDoubleQuote(); } else if ( matchTag("tnodes=\"") ) { numberSeen = TRUE ; info -> setNumberOfTnodes ( getLong() ) ; getDoubleQuote(); } else if ( matchTag("max_tnode_index=\"") ) { maxIndexSeen = TRUE ; info -> setMaxTnodeIndex ( getLong() ) ; getDoubleQuote(); } else if ( matchTag("clone_windows=\"") ) { info -> mNumberOfCloneWindows = getLong(); getDoubleQuote(); } else { getTag("/>"); break ; } }

if ( ! numberSeen ) info -> setNumberOfTnodes ( 0 ) ; if ( ! maxIndexSeen ) info -> setMaxTnodeIndex ( 0 ) ; }
@ This method reads a Leo outline from string s. @c

vnode * Commands::getLeoOutline ( String * s ) { mUsingClipboard = TRUE ; vnode * v = NULL ; // The new outline. mMemoryStream = new TMemoryStream(); mMemoryStream -> Write ( s -> c_str(), s -> Length() ) ; mMemoryStream -> Position = 0 ;

// Allocate the file buffer. mFileBufSize = 1024 * 16 ; mFileBuf = new char [ mFileBufSize ] ;

fileInfo * info = new fileInfo( mLeoForm, this ); try { readLine(); getTag(prolog_string); getTag("<leo_file>"); getClipboardHeader ( info ) ; getVnodes ( info ); getTnodes ( info ); getTag ("</leo_file>"); v = info -> finishPaste(); } catch ( const EBadLeoFile& err ) { String s ("The clipboard is not a valid Leo outline"); alert( s . c_str() ); } catch ( ... ) { String s ("Error pasting Leo outline "); alert( s . c_str() ); }

// Clean up. delete info ; delete [] mFileBuf ; delete mMemoryStream ; // 10/27/99 mMemoryStream = NULL ; // 10/27/99 mFileBuf = NULL ; mFileBufSize = 0 ; mUsingClipboard = FALSE ;

return v ; }
void Commands::getPosition ( long *top, long *left, long *height, long *width ) { *top = *left = *height = *width = 0 ; // New in version 1.7: attributes may appear in any order. for(;;) { if ( matchTag("top=\"") ) { *top = getLong(); getDoubleQuote(); } else if ( matchTag("left=\"") ) { *left = getLong(); getDoubleQuote(); } else if ( matchTag("height=\"") ) { *height = getLong(); getDoubleQuote(); } else if ( matchTag("width=\"") ) { *width = getLong(); getDoubleQuote(); } else break ; } }
void Commands::getPrefs ( fileInfo * info ) { TLeoForm * form = this -> mLeoForm ; PERM_ASSERT(form);

getTag("<preferences"); for (;;) { if ( matchTag("allow_rich_text=\"") ) { form -> mAllowRichTextFlag = getBool(); getDoubleQuote(); } else if ( matchTag("tab_width=\"") ) { form -> mTabWidth = getLong(); getDoubleQuote(); } else if ( matchTag("page_width=\"") ) { form -> mPageWidth = getLong(); getDoubleQuote(); } else if ( matchTag("tangle_bat=\"") ) { form -> mTangleBatchFlag = getBool(); getDoubleQuote(); } else if ( matchTag("untangle_bat=\"") ) { form -> mUntangleBatchFlag = getBool(); getDoubleQuote(); } // New in version 0.10 else if ( matchTag("output_doc_chunks=\"") ) { form-> mOutputDocFlag = getBool(); getDoubleQuote(); } else if ( matchTag("noweb_flag=\"") ) { #if 1 // New in version 0.19 // Ignore this flag. getBool(); getDoubleQuote(); #else form -> mUseNowebFlag = getBool(); form -> mUseCwebFlag = ! form -> mUseNowebFlag ; getDoubleQuote(); #endif } else if ( matchTag("extended_noweb_flag=\"" ) ) { #if 1 // New in version 0.19 // Ignore this flag. getBool(); getDoubleQuote(); #else form -> mExtendedNowebFlag = getBool(); getDoubleQuote(); #endif } else if ( matchTag ("defaultTargetLanguage=\"" ) ) { // New in version 0.15 << check for syntax coloring prefs >> } else if ( matchTag ("use_header_flag=\"" ) ) { form -> mUseHeaderFlag = getBool(); getDoubleQuote(); } else break; }

if ( ! matchTag("/>") ) { getTag(">");

for (;;) { if ( matchTag ("<defaultDirectory>" ) ) { // New in version 0.16. << Set the default directory preference >> getTag("</defaultDirectory>"); } else if ( matchTag("<TSyntaxMemo_options>") ) { << Set the TSyntaxMemo options >> } else break ; }

getTag("</preferences>"); } }
// Must match longer tags before short prefixes. if ( matchTag("CWEB") ) { form -> mTargetLanguage = cweb_language ; getDoubleQuote(); } else if ( matchTag("C") ) { form -> mTargetLanguage = c_language ; getDoubleQuote(); } else if ( matchTag("HTML") ) { form -> mTargetLanguage = html_language ; getDoubleQuote(); } else if ( matchTag("Java") ) { form -> mTargetLanguage = java_language ; getDoubleQuote(); } else if ( matchTag("Pascal") ) { form -> mTargetLanguage = pascal_language ; getDoubleQuote(); } else if ( matchTag("PerlPod") ) { form -> mTargetLanguage = perlpod_language ; getDoubleQuote(); } else if ( matchTag("Perl") ) { form -> mTargetLanguage = perl_language ; getDoubleQuote(); } else if ( matchTag("Plain") ) { form -> mTargetLanguage = plain_text_language ; getDoubleQuote(); } else if ( matchTag("Python") ) { form -> mTargetLanguage = python_language ; getDoubleQuote(); }

mSyntaxMemo -> Parser1 = TSMParser ( form -> mTargetLanguage ) ;
mLeoForm -> mDefaultDirectory = getEscapedString(); SetCurrentDir ( mLeoForm -> mDefaultDirectory ) ;
String s = getEscapedString(); getTag("</TSyntaxMemo_options>");

// Update the syntax settigns immediately. assert(mSyntaxMemoParser1); #if 0 // at present, this causes problems! mSyntaxMemoParser1 -> StylesFromString( s ); mSyntaxMemoParser1 -> UpdateEditors(); #endif
void Commands::getSize ( long *height, long *width ) { *height = *width = 0 ; // New in version 1.7: attributes may appear in any order. for (;;) { if ( matchTag("height=\"") ) { *height = getLong(); getDoubleQuote(); } else if ( matchTag("width=\"") ) { *width = getLong(); getDoubleQuote(); } else break ; } }
void Commands::getTnodes ( fileInfo * info ) { if ( matchTag("<tnodes>") ) { while ( matchTag("<t") ) getTnode ( info ); getTag ("</tnodes>"); } }
void Commands::getTnode ( fileInfo * info ) { // we have already matched <t. bool rtf = FALSE ; long index = -1 ; // New in version 1.7: attributes may appear in any order. for (;;) { if ( matchTag("tx=\"T") ) { index = getIndex(); getDoubleQuote(); } else if ( matchTag("rtf=\"1\"") ) rtf = TRUE ; else if ( matchTag("rtf=\"0\"") ) rtf = FALSE ; else break ; } getTag(">");

tnode * t = info -> getT ( index ) ;

if ( t == NULL ) { // No vnode refers to this tnode. String s( "No tnode with index: " ) ; s += index ; s += ". The text will be discarded" ; // alert ( s . c_str() ); es(s.c_str()); enl(); getEscapedString(); } else { String text = getEscapedString() ; if ( rtf ) t -> setTnodeRTF ( text ) ; else t -> setTnodeText ( text ) ; }

getTag ("</t>"); }
void Commands::getVnodes ( fileInfo * info ) { vnode * parent = NULL, * back = NULL ;

// We paste the first pasted vnode after the current vnode. if ( mUsingClipboard ) { back = this -> currentVnode() ; parent = back -> parent(); }

getTag("<vnodes>"); while ( matchTag("<v") ) back = getVnode ( info, parent, back );

getTag ("</vnodes>"); }
vnode * Commands::getVnode ( fileInfo * info, vnode * parent, vnode * back ) { vnode * v = NULL ; // The newly created vnode. tnode dummy_t(0,""); vnode dummy(this, &dummy_t) ; // A dummy vnode to carry status bits. bool currentVnodeFlag = FALSE ; // TRUE if the 'V' attribute seen. bool topVnodeFlag = FALSE ; // TRUE if 'T' attribute seen. long tref = 0 , vtag = 0; long xindex = 0 ; String headline("") ; String xfile("");

// we have already matched <v. for ( ;; ) { if ( matchTag("t=\"T") ) { tref = getIndex () ; getDoubleQuote(); } else if ( matchTag("vtag=\"V") ) { vtag = getIndex(); getDoubleQuote(); } else if ( matchTag("a=\"") ) { << Handle vnode attribute bits >> } else if ( matchTag ("xfile=\"") ) { // New for Leo2 xfile = getEscapedString(); getDoubleQuote(); } else if ( matchTag ("xindex=\"") ) { // New for Leo2 xindex = getIndex(); getDoubleQuote(); } else break ; } getTag(">");

// Leo2: headlines are optional. if ( matchTag("<vh>") ) { headline = getEscapedString () ; getTag("</vh>"); }

// Link v into the outline using parent and back. v = info -> createVnode ( parent, back, headline, dummy.status() /*attrib*/ , tref, vtag, xfile, xindex) ;

// Remember various info that may have been specified. if ( currentVnodeFlag ) info -> mCurrentVnode = v ; if ( topVnodeFlag ) this -> mTopVnode = v ; if ( vtag > 0 ) this -> newVtag ( vtag, v ) ;

// Recursively create all nested nodes. parent = v ; back = NULL ; while ( matchTag("<v") ) back = getVnode ( info, parent, back ) ;

// End this vnode. getTag("</v>");

return v ; }
// The a=" has already been seen. for (;;) { if ( matchChar('C') ) dummy.initClonedBit(TRUE); else if ( matchChar('D') ) dummy.setDummy(); else if ( matchChar('E') ) dummy.initExpandedBit(); else if ( matchChar('M') ) dummy.initMarkedBit(); else if ( matchChar('O') ) dummy.setOrphan(); else if ( matchChar('T') ) topVnodeFlag = TRUE ; else if ( matchChar('V') ) currentVnodeFlag = TRUE ; else break; } getDoubleQuote();
void Commands::skipWs ( void ) { while ( mLinePtr < mLineLimit ) { char c = *mLinePtr ; if ( is_ws ( c ) ) ++mLinePtr; else break ; }

// The caller is entitled to get the next character. if ( mLinePtr >= mLineLimit ) throw EBadLeoFile() ; }
@ This routine is part of the read logic, and as such must handle raw '\r' and '\n' characters. This is an adjustment to help TSyntaxMemo recognize lines properly. @c

void Commands::skipWsAndNl ( void ) { while ( mLinePtr < mLineLimit ) { char c = *mLinePtr ; if ( is_ws ( c ) || c == '\r' ) ++mLinePtr; else if ( c == '\n' ) readLine(); else break ; }

// The caller is entitled to get the next character. if ( mLinePtr >= mLineLimit ) throw EBadLeoFile() ; }
void Commands::open ( String fileName ) { mFileName = new String( fileName ) ; // Do not delete this! mLeoForm -> Caption = fileName ; << Create the TFileStream and the file buffer >> << Set the default directory >> this -> BeginUpdate(); double ratio = this -> getLeoFile ( mLeoForm ) ; this -> EndUpdate(); mLeoForm -> Show(); this -> setChanged ( FALSE ) ; this -> resizePanesToRatio ( ratio ) ; // This should be done after the pane size has been set. if ( mTopVnode ) this -> setTopVnode (mTopVnode); << Delete the TFileStream and the file buffer >> }
mFileStream = new TFileStream ( fileName, fmOpenRead | fmShareDenyWrite ) ; mFileBufSize = 1024 * 16 ; mFileBuf = new char [ mFileBufSize ] ;
@ 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

String dir = ExtractFileDir ( fileName ) ; if ( dir . Length() > 0 ) { mLeoForm -> mOpenDirectory = dir ; SetCurrentDir ( dir ) ; }
delete mFileStream ; mFileStream = NULL ;

delete [] mFileBuf ; mFileBuf = NULL ;

mFileBufSize = 0 ;
@ This method fills a line buffer from a TFileStream. There is no maximum size of a line: we reallocate mLineBuf if needed. We return FALSE if no more lines remain. Otherwise, mLinePtr points to the start of the line and mLineLimit points to the character after the newline that ends the line.

8/25/99: This routine now removes all body_ignored_newline characters. @c

bool Commands::readLine ( void ) { mLinePtr = mLineBuf ;

for ( ; ; ) { // Make sure there is room for the last character and a zero byte. if ( mLinePtr + 1 >= mLineBuf + mLineBufSize ) { << reallocate mLineBuf >> }

if ( mFilePtr >= mFileLimit ) { if ( ! mFileEmptyFlag ) { << fill mFileBuf or set mFileEmptyFlag >> } if ( mFileEmptyFlag ) { << return the last line, if any >> } }

// Get the next character. char c = *mFilePtr++ ;

// TSyntaxMemo appears to want lines ending in '\n' if ( c == '\n' ) { *mLinePtr++ = c ; *mLinePtr = '\0' ; mLineLimit = mLinePtr ; // Point mLimitPtr after the last valid character. mLinePtr = mLineBuf ; // The next character starts the line. return TRUE ; } else *mLinePtr++ = c ; // copy '\r' characters as well. } }
{ unsigned long increment = 10000 ; long offset = mLinePtr - mLineBuf ; char * oldBuf = mLineBuf ;

// Allocate and fill the new buffer. long size = mLineBufSize + increment ; mLineBuf = (char *) new char [ size ] ; assert(mLineBuf); memset(mLineBuf, 0 , size );

if ( oldBuf ) memcpy( mLineBuf, oldBuf, mLineBufSize ) ;

// Set the new buffer values. mLineBufSize += increment ; mLineLimit = mLineBuf + mLineBufSize ; mLinePtr = mLineBuf + offset ;

if ( oldBuf ) delete [] oldBuf ; }
if ( mUsingClipboard ) { // Get the next line from the memory stream. if ( mMemoryStream && ! mFileEmptyFlag ) { assert( mFileBufSize > 0 ) ; long n = mMemoryStream -> Read( mFileBuf, mFileBufSize ) ; mFileEmptyFlag = ( n == 0 ) ; mFilePtr = mFileBuf ; mFileLimit = mFileBuf + n ; } else mFileEmptyFlag = TRUE ; } else { // Get the next line from the file. if ( mFileStream && ! mFileEmptyFlag ) { assert( mFileBufSize > 0 ) ; long n = mFileStream -> Read( mFileBuf, mFileBufSize ) ; mFileEmptyFlag = ( n == 0 ) ; mFilePtr = mFileBuf ; mFileLimit = mFileBuf + n ; } else mFileEmptyFlag = TRUE ; }
if ( mLinePtr == mLineBuf ) { mLineLimit = mLinePtr ; return FALSE ; } else { // End the last line with a newline assert(mLinePtr + 1 < mLineLimit); *mLinePtr++ = body_newline ; *mLinePtr = '\0' ; mLineLimit = mLinePtr ; mLinePtr = mLineBuf ; return TRUE ; }
String Commands::xmlUnescape ( char * cs ) { char * cs1 = cs;

// Compute the length of the result. long length = 0 ; while (*cs) { ++ length ; if ( *cs++ == '&' ) { if ( strncmp ( cs, "amp;", 4 ) == 0 ) cs += 4 ; else if ( strncmp ( cs, "lt;", 3 ) == 0 ) cs += 3 ; else if ( strncmp ( cs, "gt;", 3 ) == 0 ) cs += 3 ; else --length ; // We will ignore the ampersand. } }

// Allocate the result and copy the characters into it. char * result = new char [ length + 2 ] ; long i = 0 ; cs = cs1; // rescan. while (*cs) { if ( *cs == '&' ) { ++ cs ; if ( strncmp ( cs, "amp;", 4 ) == 0 ) { cs += 4 ; result [i++] = '&'; } else if ( strncmp ( cs, "lt;", 3 ) == 0 ) { cs += 3 ; result [i++] = '<' ; } else if ( strncmp ( cs, "gt;", 3 ) == 0 ) { cs += 3 ; result [i++] = '>' ; } else ++ cs ; // Skip the ampersand: this should never happen. } else result [i++] = *cs++ ; } result [ i ] = '\0' ; assert ( i < length + 2 ) ;

// Fixes a major memory leak. String s2 ( result ) ; delete [] result ; return s2 ; }
void Commands::assignFileIndices ( void ) { for ( vnode * v = this -> rootVnode(); v ; v = v -> threadNext() ) { tnode * t = v -> t(); // 8/28/99. Write shared tnodes even if they are empty. if ( t -> hasBody() || v -> joinList() != NULL ) { if ( t -> fileIndex() == 0 ) t -> setFileIndex ( ++ mMaxTnodeIndex ) ; } else t -> setFileIndex ( 0 ) ; } }
void Commands::compactFileIndices ( void ) { mMaxTnodeIndex = 0 ;

// Clear all indices. for ( vnode * v = this -> rootVnode(); v ; v = v -> threadNext() ) { tnode * t = v -> t(); t -> setFileIndex ( 0 ) ; }

// Set indices for all shared tnodes or tnodes containing body text. for ( vnode * v = this -> rootVnode(); v ; v = v -> threadNext() ) { tnode * t = v -> t(); // 8/28/99. Write shared tnodes even if they are empty. if ( t -> hasBody() || v -> joinList() != NULL ) { if ( t -> fileIndex() == 0 ) t -> setFileIndex ( ++ mMaxTnodeIndex ) ; } } }
@ This method sets policy for when we should compact a file before doing a Save Command. @c

bool Commands::shouldCompactOnSave ( void ) { long tnodesUsed = 0 ; << Compute the number of tnodes used >> long tnodesUnused = mMaxTnodeIndex - tnodesUsed ; return ( tnodesUnused > 100 ) ; }
this -> clearAllVisited();

for ( vnode * v = this -> rootVnode(); v ; v = v -> threadNext() ) { tnode * t = v -> t(); if ( t && ! t -> isVisited() ) { ++ tnodesUsed ; t -> setVisited(); } }
@ All output eventually comes to put (char *) @c

void Commands::put ( char * s ) { if ( s == NULL ) return ; long len = strlen(s) ; if ( len == 0 ) return ;

if ( mUsingClipboard ) { << put s to the mMemoryStream >> } else { << put s to mFileStream >> } }

void Commands::put_bool ( bool b ) { put ( b ? "1" : "0" ) ; }

void Commands::put_double_quote ( void ) { put ( "\"" ) ; }

void Commands::put_nl ( void ) { put ( "\r\n" ) ; }

void Commands::put_tab ( void ) { put ( "\t" ) ; }

void Commands::put_tabs ( long n ) { while ( n-- > 0 ) put_tab(); }

void Commands::put ( String s ) { long length = s . Length();

if ( length > 0 ) { char * buffer = s . c_str(); put (buffer); assert(buffer == s . c_str()); } }

void Commands::put ( long n ) { put ( String ( n ) ) ; }

void Commands::put ( double n ) { put ( String ( n ) ) ; }
@ mFileStream will be NULL if there is any file error. @c

if ( mMemoryStream == NULL ) return ;

try { mMemoryStream -> Write ( s, len ); } catch (...) { delete mMemoryStream ; mMemoryStream = NULL ; String s("Error writing to clipboard") ; alert ( s . c_str() ) ; }
@ mFileStream will be NULL if there is any file error. @c

if ( mFileStream == NULL ) return ;

try { mFileStream -> Write ( s, len ); mFileSize += len ; } catch (...) { delete mFileStream ; mFileStream = NULL ; String s = String("Error writing to ") + ( * mFileName ) ; alert ( s . c_str() ) ; }
void Commands::putClipboardHeader ( void ) { long tnodes = 0; << count the number of tnodes >> put("<leo_header"); put(" file_format=\""); put ( "1" ) ; put_double_quote(); put(" tnodes=\""); put(tnodes); put_double_quote(); put(" max_tnode_index=\""); put( mMaxTnodeIndex ); put_double_quote(); put("/>"); put_nl(); }
this -> clearAllVisited();

// Count the vnode and tnodes. vnode * current = this -> currentVnode() ; vnode * after = current -> nodeAfterTree();

for ( vnode * v = current ; v && v != after ; v = v -> threadNext() ) { tnode * t = v -> t(); if ( t && ! t -> isVisited() && ( t -> hasBody() || v -> joinList() != NULL ) ) { t -> setVisited(); ++tnodes ; } }
void Commands::putCloneWindows ( void ) { #if USE_CLONE_WINDOW if ( mLeoForm == NULL || mLeoForm -> mCloneFormList == NULL ) return ;

put("<clone_windows>"); put_nl();

for ( TCloneForm * clone = mLeoForm -> mCloneFormList ; clone ; clone = clone -> mNextForm ) { vnode * v = clone -> mVnode ; long vtag = this -> getVtag ( v ) ; ;

put_tab(); put("<clone_window vtag=\"V"); put (vtag) ; put_double_quote(); put(">"); put_nl(); put_tabs(2); << put position of clone window >> put_tab(); put("</clone_window>"); put_nl(); }

put("</clone_windows>"); put_nl(); #endif }
long top = clone -> Top; long left = clone -> Left; long height = clone -> Height; long width = clone -> Width;

put("<global_window_position"); put(" top=\""); put( top ); put_double_quote(); put(" left=\""); put( left ); put_double_quote(); put(" height=\""); put( height ); put_double_quote(); put(" width=\""); put( width ); put_double_quote(); put("/>"); put_nl();
@ Surprisingly, the call to xmlEscape here is _much_ faster than calling put for each characters of s.c_str(). @c

void Commands::putEscapedString ( String s ) { put ( this -> xmlEscape ( s ) ) ; }
#define put_flag(a,b) if (a) { put(" "); put(b); put("=\"1\""); }

void Commands::putFindSettings ( void ) { put("<find_panel_settings"); put_flag ( batch_flag, "batch" ) ; put_flag ( wrap_flag, "wrap" ) ; put_flag ( whole_word_flag, "whole_word" ) ; put_flag ( ignore_case_flag, "ignore_case" ) ; put_flag ( pattern_match_flag, "pattern_match" ); put_flag ( search_headline_flag, "search_headline" ); put_flag ( search_body_flag, "search_body" ); put_flag ( suboutline_only_flag, "suboutline_only" ); put_flag ( mark_changes_flag, "mark_changes" ); put_flag ( mark_finds_flag, "mark_finds" ); put_flag ( reverse_flag, "reverse" ); put(">"); put_nl(); put_tab(); put("<find_string>"); putEscapedString(find_text); put("</find_string>"); put_nl(); put_tab(); put("<change_string>"); putEscapedString(change_text); put("</change_string>"); put_nl(); put("</find_panel_settings>"); put_nl(); }

#undef put_flag
void Commands::putGlobals ( void ) { long top, left, height, width ;

put("<globals"); << put the body/outline ratio >> put(">"); put_nl(); << put the position of this form >> << put the position of the log window >> put("</globals>"); put_nl(); }
{ long treeViewH = mTreeView -> Height ; long richViewH = mSyntaxMemo -> Height ; double ratio = double(treeViewH) / (double) (treeViewH + richViewH) ;

put(" body_outline_ratio=\""); #if 0 // Puts an innumerate number of digits put ( ratio ); #else char buffer [ 50 ] ; sprintf( buffer, "%2.3f", ratio ); put ( buffer ) ; #endif put_double_quote(); }
top = mLeoForm -> Top ; left = mLeoForm -> Left; height = mLeoForm -> Height ; width = mLeoForm -> Width ;

put_tab(); put("<global_window_position"); put(" top=\""); put( top ); put_double_quote(); put(" left=\""); put( left ); put_double_quote(); put(" height=\""); put( height ); put_double_quote(); put(" width=\""); put( width ); put_double_quote(); put("/>"); put_nl();
top = SherlockForm -> Top ; left = SherlockForm -> Left; height = SherlockForm -> Height ; width = SherlockForm -> Width ;

put_tab(); put("<global_log_window_position"); put(" top=\""); put( top ); put_double_quote(); put(" left=\""); put( left ); put_double_quote(); put(" height=\""); put( height ); put_double_quote(); put(" width=\""); put( width ); put_double_quote(); put("/>"); put_nl();
void Commands::putHeader ( void ) { long tnodes = 0, clone_windows = 0 ; #if 0 // These counts are not used in Leo2. << count the number of tnodes and clone windows >> #endif put("<leo_header"); // Leo2 use file format 2 so that Leo1 can warn about altering Leo2 files. put(" file_format=\""); put ( "2" ) ; put_double_quote(); put(" tnodes=\""); put(tnodes); put_double_quote(); put(" max_tnode_index=\""); put( mMaxTnodeIndex ); put_double_quote(); put(" clone_windows=\""); put(clone_windows); put_double_quote(); put("/>"); put_nl(); }
this -> clearAllVisited();

// Count the vnode and tnodes. for ( vnode * v = this -> rootVnode(); v ; v = v -> threadNext() ) { tnode * t = v -> t(); // 8/28/99: we write all shared tnodes, even if empty. if ( t && ! t -> isVisited() && ( t -> hasBody() || v -> joinList() != NULL ) ) { t -> setVisited(); ++tnodes ; } }

#if USE_CLONE_WINDOW // Count the clone windows. if ( mLeoForm ) { for ( TCloneForm * clone = mLeoForm -> mCloneFormList ; clone ; clone = clone -> mNextForm ) ++clone_windows ; } #endif // USE_CLONE_WINDOW
@ This method writes a Leo outline to the clipboard. @c

bool Commands::putLeoOutline ( TMemoryStream * ms ) { mMemoryStream = ms ; // Create an alias. mUsingClipboard = TRUE ; // assignFileIndices(); // The caller does this. putProlog(); putClipboardHeader(); putVnodes(); putTnodes(); putPostlog(); bool ok = ( mMemoryStream != NULL ) ; // The caller will delete ms. // Do _not_ delete mMemoryStream; it is just an alias. mMemoryStream = NULL ; mUsingClipboard = FALSE ; return ok ; }
void Commands::putPrefs ( void ) { TLeoForm * form = this -> mLeoForm ; PERM_ASSERT(form);

put("<preferences"); put(" allow_rich_text=\""); put_bool( default_allow_rich_text ); put_double_quote(); put(" tab_width=\""); put( default_tab_width ); put_double_quote(); put(" page_width=\""); put( default_page_width ); put_double_quote(); put(" tangle_bat=\""); put_bool( default_tangle_batch ); put_double_quote(); put(" untangle_bat=\""); put_bool( default_untangle_batch ); put_double_quote(); // New in version 0.10 put(" output_doc_chunks=\""); put_bool( default_output_doc_flag ); put_double_quote(); << put syntax coloring prefs >> // New in version 0.15 // New in version 0.18 put(" use_header_flag=\""); put_bool( default_use_header_flag ); put_double_quote(); // Removed in version 0.19 #ifdef USE_CWEB put(" noweb_flag=\""); put_bool( default_use_noweb_flag ); put_double_quote(); put(" extended_noweb_flag=\""); put_bool( default_extended_noweb_flag ); put_double_quote(); #endif put(">"); put_nl(); put_tab(); << put default directory >> // New in version 0.16 put_nl(); put("<TSyntaxMemo_options>"); putEscapedString ( mSyntaxMemoParser1 -> StylesAsString() ) ; put ("</TSyntaxMemo_options>"); put_nl(); put("</preferences>"); put_nl(); }
String key = " defaultTargetLanguage=\"" ;

switch ( default_target_language ) { case c_language: put(key); put("C\""); break; case cweb_language: put(key); put("CWEB\""); break; case html_language: put(key); put("HTML\""); break; case java_language: put(key); put("Java\""); break; case pascal_language: put(key); put("Pascal\""); break; case perl_language: put(key); put("Perl\""); break; case perlpod_language: put(key); put("PerlPod\""); break; case plain_text_language: put(key); put("Plain\""); break; case python_language: put(key); put("Python\""); break; default: put("Plain\""); }
if ( mLeoForm -> mDefaultDirectory . Length() > 0 ) { put("<defaultDirectory>"); putEscapedString ( mLeoForm -> mDefaultDirectory ) ; put ("</defaultDirectory>"); }
void Commands::putProlog ( void ) { put(prolog_string); put_nl(); // internal or external DTD goes here put("<leo_file>"); put_nl(); }
void Commands::putPostlog ( void ) { put("</leo_file>"); put_nl(); }
@ This method puts all tnodes in index order. All tnode indices must have been assigned at this point. @c

void Commands::putTnodes ( void ) { if ( mMaxTnodeIndex < 1 ) return ;

tnode ** tnodes = (tnode **) new char [ sizeof (tnode *) * (mMaxTnodeIndex+1) ] ;

for ( long i = 0 ; i < mMaxTnodeIndex ; ++ i ) tnodes [i] = NULL ;

// Populate the tnodes array. if ( mUsingClipboard ) { // Just write the current tree. vnode * current = this -> currentVnode(); vnode * after = current -> nodeAfterTree(); for (vnode * v = current; v && v != after ; v = v -> threadNext() ) { tnode * t = v -> t(); long index = t -> fileIndex(); if ( index > 0 ) { if ( index <= mMaxTnodeIndex && (tnodes[index-1]==NULL || tnodes[index-1]== t ) ) tnodes [ index - 1 ] = t ; else alert ("error writing to clipboard"); } } } else { // Write the entire outline. for (vnode * v = this -> rootVnode(); v; v = v -> threadNext() ) { tnode * t = v -> t(); long index = t -> fileIndex(); if ( index > 0 ) { if ( index <= mMaxTnodeIndex ) tnodes [ index - 1 ] = t ; else alert( "error writing file (bad tnode)! Try using the Save To command"); } } }

// Put all tnodes in index order. put("<tnodes>"); put_nl(); for ( long i = 0 ; i < mMaxTnodeIndex ; ++ i ) { // New for Leo2: write only those tnodes whose vnodes were written. tnode * t = tnodes [i] ; if ( t && t -> isVisited() ) put ( t ) ; } put("</tnodes>"); put_nl();

delete [] tnodes ; }
void Commands::put ( tnode * t ) { assert(t); String bodyString = t -> bodyString(); String bodyRTF = t -> bodyRTF(); bool hasBody = t -> hasBody(); bool hasRichText = arg_allow_rich_text && t -> isRichTextBit() && bodyRTF . Length() > 0;

put("<t tx=\"T"); put( t -> fileIndex() ); put_double_quote(); if ( hasBody && hasRichText ) put(" rtf=\"1\""); put(">");

if ( hasBody && hasRichText ) putEscapedString( bodyRTF ) ; else if ( hasBody && bodyString . Length() > 0 ) putEscapedString( bodyString ) ;

put("</t>"); put_nl(); }
@ This method puts all vnodes by starting the recursion. put(vnode*) will write all vnodes in the order in which they appear in the outline. @c

void Commands::putVnodes ( void ) { // Set the global. mMaxVnodeTag = 0 ;

TTreeView * treeView = this -> treeView(); TTreeNode * topNode = treeView -> TopItem ; vnode * topVnode = (vnode *) topNode -> Data ; assert(topVnode);

// New for Leo2: put(vnode*) will visit // only those tnodes whose vnodes were written. this -> clearAllVisited();

put("<vnodes>"); put_nl(); if ( mUsingClipboard ) put ( this -> currentVnode(), // Write only current tree. NULL, // Don't write top vnode status bit. TRUE, // forceFlag. FALSE); // dummyFlag (don't care). else for ( vnode * v = this -> rootVnode() ; v ; v = v -> next() ) put ( v, // Write the next top-level node. topVnode, // Write the top-vnode status bit. FALSE, // forceFlag, FALSE); // dummyFlag. put("</vnodes>"); put_nl(); }
@ When forceFlag is TRUE we generate full vnodes for v's tree. Otherwise, if dummyFlag is TRUE we generate dummy vnodes only. In particular, we generate dummy vnodes for all valid, non-ignored @file nodes.

atFile::write has already set the orphan bit for some nodes, including all invalid @file nodes. However, we must make a separate check for atIgnored nodes. @c

void Commands::put ( vnode * v, vnode * topVnode, bool forceFlag, bool dummyFlag ) { atFile * at = new atFile( this );

// The heart of Leo2's write code. bool isAtFile = v -> isAtFileNode(); bool isIgnore = v -> isAtIgnoreNode(); bool orphanFlag = v -> isOrphan() || isIgnore; forceFlag = forceFlag || orphanFlag ; #if 0 // We always write the headline now. bool writeHead = forceFlag || !dummyFlag || isAtFile; #endif dummyFlag = !forceFlag && ( dummyFlag || isAtFile );

put("<v"); if ( !dummyFlag || v -> joinList() != NULL ) { << Put tnode index if this vnode has body text >> } << Put a vnode tag if this vnode has a clone window >> << Put attribute bits >> put(">"); << write the head text >>

vnode * child = v -> firstChild() ; if ( child ) { put_nl(); while ( child ) { put ( child, topVnode, forceFlag, dummyFlag ) ; child = child -> next(); } } put("</v>"); put_nl();

delete at ; }
tnode * t = v -> t();

if ( t && t -> hasBody() || v -> joinList() != NULL ) { if ( t -> fileIndex() > 0 ) { put (" t=\"T"); put ( t -> fileIndex() ); put_double_quote(); v -> t() -> setVisited(); // Indicate we wrote the body text. } else { alert("error writing file (bad vnode)! Try using the Save To command"); } }
#if USE_CLONE_WINDOW

// See if any clone window has this vnode as its master. if ( mLeoForm ) { bool found = FALSE ;

for ( TCloneForm * form = mLeoForm -> mCloneFormList ; form && ! found ; form = form -> mNextForm ) if ( v == form -> mVnode ) found = TRUE ;

if ( found ) { long tag = ++mMaxVnodeTag ; this -> newVtag ( tag, v ) ; put(" vtag=\"V"); put ( tag ); put_double_quote(); } }

#endif
@ Dummy vnodes carry all attributes. @c

vnode * current = this -> currentVnode(); vnode * top = this -> topVnode();

if ( v -> isCloned() || v -> isExpanded() || v -> isMarked() || forceFlag || dummyFlag || v == current || v == top ) { put(" a=\""); if ( v -> isCloned()) put("C"); if ( dummyFlag ) put("D"); if ( v -> isExpanded()) put("E"); if ( v -> isMarked()) put("M"); if ( orphanFlag ) put("O"); if ( v == top ) put("T"); if ( v == current ) put("V"); put_double_quote(); }
@ Dummy vnodes have no headline text. @c

assert(v -> treeNode()); String headString = v -> treeNode() -> Text ;

if ( headString . Length() > 0 ) { put("<vh>"); putEscapedString( headString ); put("</vh>"); }
bool Commands::save ( String fileName ) { this -> endEditing () ; // Set the current headline text.

if ( this -> shouldCompactOnSave() ) this -> compactFileIndices(); else this -> assignFileIndices();

try { if ( this -> write_LEO_file ( fileName, FALSE /* outlineOnlyFlag */ ) ) { this -> setChanged ( FALSE ) ; // Clears all dirty bits. this -> Repaint(); return TRUE; } } catch ( ... ) {} return FALSE; }
bool Commands::saveAs ( String fileName ) { this -> endEditing () ; // Set the current headline text. this -> compactFileIndices() ;

try { if ( this -> write_LEO_file ( fileName, FALSE /* outlineOnlyFlag */ ) ) { this -> setChanged ( FALSE ) ; // Clears all dirty bits. this -> Repaint(); return TRUE; } } catch ( ... ) {} return FALSE; }
bool Commands::saveACopyAs ( String fileName ) { try { this -> endEditing () ; // Set the current headline text. this -> compactFileIndices() ; this -> write_LEO_file ( fileName, FALSE /* outlineOnlyFlag */ ) ; return TRUE; } catch ( ... ) {} return FALSE; }
bool Commands::saveOutlineOnlyTo ( String fileName ) { try { this -> endEditing () ; // Set the current headline text. this -> compactFileIndices() ; this -> write_LEO_file ( fileName, TRUE /* outlineOnlyFlag */ ) ; return TRUE; } catch ( ... ) {} return FALSE; }
@ Surprisingly, this is a time critical routine. This code is much faster than creating the result string by concatenations. It is also much faster to use this routine than calling put() repeatedly for each character of s.c_str().

8/25/99: We enforce the end-of-line convention here by turning bare '\r' or '\n' characters into cr/lf sequences. This is an adjustment for TSyntaxMemo's difficulty recognizing lines that start with only '\r'. @c

String Commands::xmlEscape ( String s ) { char * cs = s . c_str() ; char * cs1 = cs;

// Compute the length of the result. long length = 0 ; while (*cs) { switch ( *cs++ ) { case '&': length += 5 ; break; case '<': length += 4 ; break; case '>': length += 4 ; break; // Convert any combination of cr or lf to cr/lf. case '\r': length += 2 ; if ( *cs == '\n' ) ++cs; break; case '\n': length += 2 ; break; default : ++ length ; break ; } }

// Allocate the result and copy the characters into it. char * result = new char [ length + 2 ] ; long i = 0 ; cs = cs1; // rescan. while (*cs) { switch ( *cs++ ) { case '&': strcpy( result + i, "&amp;" ) ; i += 5; break; case '<': strcpy( result + i, "&lt;" ) ; i += 4; break; case '>': strcpy( result + i, "&gt;" ) ; i += 4; break; case '\r': strcpy ( result + i, "\r\n" ) ; i += 2 ; if ( *cs == '\n') ++cs; break ; case '\n': strcpy ( result + i, "\r\n" ) ; i += 2 ; break; default : result [ i ] = *(cs-1) ; ++i ; break ; } } result [ i ] = '\0' ; assert ( i < length + 2 ) ;

String s2 ( result ) ; delete [] result ; return s2 ; }
@ The caller does any repainting. @c

bool Commands::write_LEO_file ( String fileName, bool outlineOnlyFlag ) { // Leo2: write all @file nodes and set orphan bits. if ( ! outlineOnlyFlag ) { atFile * at = new atFile( this ); at -> writeAll ( this -> rootVnode() ) ; delete at ; }

<< Open fileName and buffer it >> putProlog(); putHeader(); putGlobals(); putPrefs(); putFindSettings(); putVnodes(); putTnodes(); putCloneWindows(); putPostlog(); bool ok = ( mFileStream != NULL ) ; << Close the file >> deleteTagList();

return ok ; }
delete mFileName ; mFileName = new String( fileName ) ; mFileSize = 0 ;

try { mFileStream = new TFileStream ( fileName, fmCreate | fmShareDenyWrite ) ; } catch (...) { mFileStream = NULL ; }
if ( mFileStream ) { mFileStream -> Size = mFileSize ; // Truncate the file. delete mFileStream ; mFileStream = NULL ; }
@nocolor

These papers discuss topics related to Leo.
This paper presents my present thoughts about recovering from "read errors", that is, errors encountered while reading @file trees.

Recall that read errors will occur when the .leo file is out of synch with the derived files that it creates. Read errors break clone links into the derived file, a highly undesirable situation. Read errors will occur when restoring a project from a backup file. They can also occur when using CVS if derived files aren't checked out with the .leo file that created them.

No More "False" Read Errors

leo.py v 0.8 fixed a bug that caused some changed clone nodes not to be marked dirty. I haven't suffered a read error since then. "False" read errors, that is, read errors encountered while reading derived files that are in synch with the .leo files, seem to be gone forever.

Note: This bug still exists in the Borland version of Leo. I plan to fix this and all other known bugs in the Borland version in the next week or so.

No More Broken Clone Links

Even though "false" read errors are gone, read errors can still occur when the .leo file is out of synch with derived files. To repeat: this can happen when using a backup .leo file, or when failing to check out all files in synch from CVS.

At present Leo restores the @file tree in the outline from the derived file when a read error occurs, breaking clone links into the derived file in the process. Leo could retain clone links if it recovered from read errors using the outline rather than the derived file. This way has the following features:

1. We retain clone links by default. The user doesn't have to understand all the issues involved when read errors occur. In particular, the user won't be confronted with an dialog when read errors occur that requires the user to make an obscure choice. Leo will clearly state that data is recovered from the outline rather than the derived file.

2. When restoring a project from a backup .leo file, it is natural to have the information in the .leo file take precedence over the derived file. Ditto when using CVS.

3. The user can "manually" restore information from a derived file using the Read @file Nodes command if necessary. This operation, and only this operation, will break clone links.
Let us consider how to use CVS with Leo. Our goal is this:

CVS operations should retain clone links.

In general, clone links will break whenever Leo reads a derived file from a .leo file different from the .leo file that created it. This leads immediately to

-- Rules for using Leo files with CVS --

1. All derived files in a particular revision in the CVS repository must be generated by a single .leo file. Call that .leo file the _generating_ .leo file (for the revision as a whole and for each derived file in particular). In other words, all .leo files in the repository should be proper snapshots of their revision.

2. When we check files out of the repository, we must check out their generating .leo file.

3. When we check files into the repository, we must check in their (unique!) generating .leo file. Developers should use Write @file Nodes command to ensure that all derived files are in synch with this .leo file.

4. When CVS detects update conflicts, developers should resolve those conflicts into a single "merged" .leo file. Again, developers should use Write @file Nodes to recreate all derived files.

5. Developers should retain clone links when creating a merged .leo file. Most merges will have to be done "by hand" by starting with one .leo file and pasting changes from the other .leo file into it.

In short: Developers should include generating .leo files in all CVS operations.

We could conceivably go further and include _only_ .leo files in CVS operations. We would check out only .leo files, creating derived files as needed using the Write @file Nodes command. One drawback to this scheme is that the repository would contain only .leo files, so all diffs would be based on .leo files. This will make detecting conflicts more difficult. Another drawback is that the repository would contain no human readable files. So probably we should include both .leo files and derived files in the repository.

Rules 4 and 5 imply that there is extra work to be done when using Leo with CVS. That should not be too surprising; there are extra files (the .leo files) to update.

I always envisioned that update conflicts would have to be resolved in .leo files rather than derived files. It is now clear that this is the only way to prevent clone links from being broken.

I believe these rules are correct. If you disagree with these conclusions please let me know immediately.

Edward
My last posting is completely wrong. Leo can not mend clone links for a very simple reason: cloned nodes must have the same structure! Most read errors come about because Leo has detected that nodes do _not_ have the same structure.

Even if a Mend Clone dialog were put up, the user would have no way of knowing whether any joins made sense. Joining two nodes with different structure would be disastrous.

Mending clones would be possible only if the only differences between trees were differences in headline text. This suggests that Leo shouldn't test headline text during reads. This would eliminated false read errors. This is an easy change and I plan to test it soon.

My next posting will try to make a virtue out of necessity by proposing guidelines for using Leo with CVS.

Edward
Development work on Leo is about to start using CVS. This raises a number of questions about how to use Leo projects with CVS. Resolving these issues is very important; people won't use Leo if it doesn't work well with CVS.

1. CVS may not do very well in reporting conflicts involving .leo files. Indeed, .leo files might almost be considered to be "binary" files as far as CVS is concerned. Experience will show how serious this problem turns out to be, and I have some ideas about how to help out CVS.

2. Files derived from @root or @file trees contain structure information that should not be edited by hand. In particular, if CVS reports a conflict, that conflict should be resolved at the outline level, not in the derived file. Again, it remains to be seen how big a problem this will be, and again I have some ideas about how to make things more smooth.

Note: It will never be possible to update outline _structure_ using the Untangle command. Indeed, the Untangle command traverses the outline to find body text to update; there is absolutely no way for Untangle to change outline structure. It _is_ possible to update outline structure by reading @file trees (that was a big reason for creating @file trees!), and there are problems with clones. See the next section.
This note addresses problem 1 of my initial posting, namely that .leo files are like "binary" files as far as CVS is concerned. That is, CVS may have problems providing intelligent diffs of .leo files.

It would be easy for Leo to write a "diff-friendly" version of a .leo file. Call such files .cvs.leo files. .cvs.leo files would be easy for us humans to understand. I am thinking that people would check .cvs.leo files in and out rather than .leo files. On checkout, one would then recreate .leo files from .cvs.leo files. Before checkin, one would create .cvs.leo files from .leo files.

A .cvs.leo file would be a text file, but not an XML file. It would have the following format:

1. header line: // created by leo: <file name>

The information found in the <leo_header>, <globals>, <preferences> and <find_panel_settings> parts of the present .leo file format would be omitted. Such information is not needed and will only confuse diff.

2. vnode (outline) section

Optional. A list of headline names without status bits and without tnode indices. It indicates the structure of the outline.

3. separator line

Optional. It might make it easy for diff to distinguish the vnode section from the tnode section.

4. tnode (full outline) section:

This would be easy for humans to understand and would be like the format used in the Import/Export MORE text commands:

Each headline on separate line, starts with + or -. The + or = depends only on children: the expansion state of the headline doesn't matter. The headline is followed by body text, with escapes for + and - so that body text can never be confused with headlines.

Still unresolved is a way of specifying clones in a bullet-proof way.

Edward
This note is another approach to handling some of the problems mentioned in my original post.

It may be desirable to use @cvs trees instead of @file tree. When Leo writes a @cvs tree to a .leo file it writes only the headline of the tree; it writes none of the headlines below the @cvs tree and writes none of the body text of the tree. This is suspiciously like the infamous "Archive" command, and it may be a good idea nevertheless in the context of CVS. With @cvs, the .leo file becomes just a dummy file, so it would not be common to check .leo files in or out.

Implementing this command can be done easily. The question is, does it make sense to do so?
The more I look at Tk, the more convinced I am that Python + Tk (aka tkinter) is, by far, the best way to go with Leo.py. I now have Open Source code for tree widgets and splitter windows, and have intensely studied how to modify that code for use in Leo. It is clear, even at this early date, that this code will provide a very pleasant base on which to build Leo.

The tree code is based on code in IDLE, the Python IDE. This code is simple, good and plenty fast enough. The tree code draws directly to a Tk canvas object. The look and feel matches Windows exactly. It would be trivial to use Mac triangle icons instead of the Windows plus and minus icons. It would also be trivial to modify the look and feel for Linux.

The tree widget code solves several intractable problems with wxTreeCtrl. Moving nodes becomes trivial. Bugs in wxTreeCtrl involving editing and redrawing disappear. Using Python/Tk code simplifies the vnode class, and having access to the vnode class simplifies and speeds up the tree widget code. There is no SWIG overhead in calling the wxTreeCtrl code. It will now be possible to bind keystrokes properly; this simply can not be done in wxWindows.

Actually, the tree widget code shows just how trivial the Windows native tree control is. Of course, the Tk canvas class simplifies matters, but there are several important points here:

1. The wxTreeCtrl tree class actually does very little, and does it rather badly. 2. There is absolutely no need for C++ code (except in the Tk canvas class) to draw trees. 3. Leo can _easily_ have _complete_ control over the look and feel of tree controls. 4. I can modify in the tree code to simplify the rest of Leo, especially Leo's vnode class.

Here we have a splendid example of higher-level code being superior, in _every_ way, to lower level code. The wxTreeCtrl code is horrendously complex, and in fact does almost nothing!

Similar remarks apply to the splitter window. The Tk code for splitter windows is about a page long. No code could be higher level because the Tk code describes only _what_ is to be done, not _how_ it is too be done.

Another big win comes from using the Tk text widget. This widget is extraordinarily powerful. The only text control that rivals it is the MacOS/Yellow Box text control. Indeed, the Tk text widget does everything that Leo could possibly want. One can even embed images in text!

Syntax coloring Tk text will be straightforward in Python. IDLE's syntax coloring is very fast on modern workstations. There will be no need to use or modify the Scintilla C++ code.

In short, using Tk for Leo.py will be fast enough and will greatly increase what is possible in Leo while at the same time greatly simplifying Leo's code. I am about to convert Leo.py from wxPython to Python + Tk.

Edward K. Ream November 4, 2001
I've known for a while that Python was interesting; I attended a Python conference last year and added Python support to Leo. But last week I got that Python is something truly remarkable. I wanted to convert Leo from wxWindows to wxPython, so I began work on c2py, a Python script that would help convert from C++ syntax to Python. While doing so, I had an Aha experience. Python is more than an incremental improvement over Smalltalk or C++ or objective-C; it is "something completely different". The rest of this post tries to explain this difference.

Clarity: no clutter, easy prototyping -------------------------------------

What struck me first as I converted C++ code to Python is how much less blah, blah, blah there is in Python. No braces, no stupid semicolons and most importantly, _no declarations_. No more pointless distinctions between const char *, char const *, char * and wxString. No more wondering whether a variable should be signed, unsigned, short or long.

Declarations add clutter, declarations are never obviously right and declarations don't prevent memory allocation tragedies. Declarations also hinder prototyping. In C++, if I change the type of something I much change all related declarations; this can be a huge and dangerous task. With Python, I can change the type of an object without changing the code at all! It's no accident that Leo's new log pane was created first in Python.

Functions returning tuples are a "minor" feature with a huge impact on code clarity. No more passing pointers to data, no more defining (and allocating and deallocating) temporary structs to hold multiple values.

Python can't check declarations because there aren't any. However, there is a really nifty tool called Pychecker that does many of the checks typically done by compilers. See http://pychecker.sourceforge.net/ for details.

Power -----

Python is much more powerful than C++, not because Python has more features, but because Python needs _less_ features. Some examples:

1. Python does everything that the C++ Standard Template Library (STL) does, without any of the blah, blah, blah needed by STL. No fuss, no muss, no code bloat.

2. Python's slicing mechanism is very powerful and applies to any sequence (string, list or tuple). Python's string library does more with far less functions because slices replace many functions typically found in other string libraries.

3. Writing dict = {} creates a dictionary (hash table). Hash tables can contain anything, including lists and other hash tables.

4. Python's special functions, like __init__, __del__, __repr__, __cmp__, etc. create an elegant way to handle any special need that might arise.

Safety ------

Before using Python I never fully realized how difficult and dangerous memory allocation is in C++. Try doing

aList[i:j] = list(aString)

in C. You will write at least 10 - 20 lines of code. Any error in this code will create a memory allocation crash or leak.

Python is fundamentally safe. C++ is fundamentally unsafe. When I am using Python I am free from worry and anxiety. When I am using C++ I must be constantly "on guard." A momentary lapse can create a hard-to-find pointer bug. With Python, almost nothing serious can ever go wrong, so I can work late at night, or after a few beers.

And the Python debugger is always available. If an unexpected exception occurs, the debugger/interpreter tells me just what went wrong. I don't have to plan a debugging strategy! Finally, wxPython recovers from exceptions, so Leo can keep right on going even after a crash!

Code Speed ----------

Python has almost all the speed of C. Other interpretive environments such as icon and smalltalk have clarity, power and safety similar to Python. What makes Python unique is its seamless way of making C code look like Python code.

Much of Python executes at essentially the speed of C code because most Python modules are written in C. The overhead in calling such modules is negligible. Moreover, if code is too slow, one can always create a C module to do the job.

In fact, Python encourages optimization by moving to _higher_ levels of expression. For example, Leo's Open command reads an XML file. If this command is too slow I can use Python's XML parser module. This will speed up Leo while at the same time raising the level of the code.

Conclusions -----------

Little of Python is completely new. What stands out is the superb engineering judgment evident in Python's design. Python is extremely powerful, yet small, simple and elegant. Python allows me to express my intentions clearly and at the highest possible level.

The only hope of making Leo all it can be is to use the best possible tools. I believe Python (possibly with tkinter) will allow me to add, at long last, the new features that Leo should have.

Edward K. Ream October 25, 2001