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
Quotes of the Month
Anon
Tarvin Rhodes
Anon
Anon
From Michael Dawson
From David Nichols
From William A. Stapp
From Dan Winkler
Annonymous
Speed Ream's slashdot article (Why Leo is interesting)
Open Software award nominations
Alex Abacus
Steve Allen
Shakeeb Alireza
John Benson
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 on MacOS Panther (10.3)
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 versions
4.1 final
Added several new plugins
Added Toggle Angle Brackets command
Changed Undo/Redo messages
Fixed clone bug when pasting nodes
Investigated encoding problem: it's a Python 2.3 bug
leo.py versions
4.1 final
Added several new plugins
Added Toggle Angle Brackets command
Changed Undo/Redo messages
Fixed clone bug when pasting nodes
Investigated encoding problem: it's a Python 2.3 bug
4.1 rc4
New plugins in 4.1 rc4
mod_http.py plugin: Bernhard Mulder
newButtons.py plugin: Paul Paterson
nodenavigator.py plugin: Paul Paterson
rowcol.py: EKR
rst2.py plugin: Steve Zatz
searchbox.py plugin: Paul Paterson
status_line.py plugin: EKR
New features in 4.1 rc4
Leo no longer automatically draws the status area
Esc and enter key now work in the find panel
Improved message for orphan nodes
Eliminated marking nodes dirty unnecessarily
Execute Script command now ends all nodes with a newline
Added message when plugins are disabled
Created status_line.py plugin
Simplified operation of script-find/change & improved documentation
Script Find and Script Change
How it works
Using the initScriptFind script
Ideas for using scripts
Added done message to Mark Changed commands
Bugs fixed in 4.1 rc4
Fixed several bugs when writing derived files
Fixed several crashers
Fixed "bad leo file" when reading some 3.x .leo files
Fixed huge performance bug in cut/copy node commands
Leo now properly re-enables drawing after exceptions
Fixed major bug in tkFind.init
Fixed bug in Move Up command
Fixed bug in Import Derived File
Fixed bugs related to saving read-only files
Fixed bug in Import Python Window command when using Python 2.2
Fixed bugs in Convert All BlanksTabs commands
Fixed crashes in open_shell.py plugin
Fixed several bugs involving cut & paste
Fixed whitespace problem in leoTkinterGui.py
Fixed glitches when inserting node in hoisted outline
Fixed bugs in search command
Bug fixed in previous 4.1 releases
New code-level features
New configuration settings
New features
4.1 rc3
Bugs fixed in this release
Bug fixed in previous 4.1 releases
New code-level features
New configuration settings
New features
4.1 beta 5
Changes made in 4.1 beta 5
New and improved features
Honor outline/body pane ratio when opening files
Leo now adjusts newly opened windows so they fit on the screen
Tested support for Unicode file & directory names
Added code so @url nodes may open .leo files
Fixed problems with @lineending platform
Bugs fixed
Fixed crasher in Apply Settings command
Fixed crasher in Toggle Angle Brackets command
Fixed crasher when setting Icon in Linux)
Fixed Find panel bug on the Mac
Fixed Import backslash-newline bug
Fixed Linux import problem
Fixed Recent files crasher
Fixed Replace Blanks command
Fixed Serious Undo Change All bugs
Fixed several long-standing bugs related to Extract commands
Changes made in earlier 4.1 betas
Code-level features
Regression tests started
Code reorganized to support other guis
Made app.forceShutdown work in "idle" hook
Fixed bugs
Fixed bug involving 0x1a characters in body text.
Fixed first node-only find bug
Fixed bug in idle_body_key
Fixed LaTeX syntax coloring bug
Improved format of .leo files
Leo now ignores unknown prefs and find prefs
Leo now writes node_only find prefs
.leo files are now cvs-friendly
New & improved commands
Running Leo in batch mode
Executing Python scripts in body text
Added Check Outline command
Added Hoist/De-Hoist commands
Added script-based find/change commands
Script Find and Script Change
How it works
Using the initScriptFind script
Ideas for using scripts
Leo now does an auto-save only if files have actually been changed.
Shift commands now shift single line if no selection
New config settings
4.1 alpha 1
4.0.x
4.0 final
4.0 beta 4
4.0 beta 3
Fixed bugs
Fixed bug that caused some clones not to be marked as clones
Corrected problems with setting icons on Linux
Fixed crasher in sentinelName
Reenabled orphans & ignored test in new write logic
New features
Added support for unknown tnode and vnode attributes
Added build number to signon
Added better error message in setDefaultIcon
4.0 beta 2
To-do for 4.0 final
Fixed Bugs
Added crucial defensive checks in v.headString() and v.bodySting()
Ensured newlines before all sentinels in @rawfile
Fixed bugs involving find params
Fixed Python Import bug
Fixed several more unicode bugs
Fixed crasher in Go To Outline command.
Fixed @rawfile write problem
Improved commands
Finished Import Derived File command and made it undoable
Moved Clear Recent Files menu item into Recent Files menu
Added better log message for Convert All Tabs/Blanks commands
Marked changed nodes & make the @file node and the outline dirty.
Rewrote icon code so it doesn't use PIL or tkIcon in Python 2.3
Created leoGui base class for tkinterGui class
New and improved settings
Added support for two new settings affecting drags
changed default for config.write_old_format_derived_files
4.0 beta 1
4.0 Theory of operation
New commands & options
New write_old_format_derived_files option
New Clear Recent Files command
New Import Derived File command
New Write 3.x/4.x Derived Files & autosave
New plugins
New read logic and greatly inproved error recovery
Added test for unvisited nodes in read logic
Error recovery is _much_ better than before
Improvements & bug fixes
Added horizontal scrollbar in body pane when @nowrap in effect
Added support for Python 2.3
Leo recycles window objects only if more than one window open
Removed "not undoable" message from Read @file Nodes command
Removed Open Python Window command
Simplified read and write code
3.x releases
3.12 final
Bug fixes (major)
Fixed major undo bug
Fixed huge performance bug when handling read errors
Fixed memory leaks: one minor, one huge
Bug fixes (minor)
Fixed bug affecting encoding in headlines
Fixed bug affecting log pane
Fixed bug importing files with upper case extensions
Fixed bug involving log messages during Open commands
Fixed bug that caused some log messages to disappear
Fixed bug that disabled double-clicking when when windows were first opened
Fixed bug that forced body_pane_wraps setting to 1
Fixed bug when deleting trailing newlines in the body pane
Fixed colorizer exception when undoing paste
Fixed Go To Line Number bug
Fixed line-break bug with @nosent files
Fixed new Open With problems
Fixed problems with Convert tabs to blanks preference panel setting
Fixed problems with the code that destroys Leo windows
Fixed recent bug in the Reformat Paragraph command
Fixed refresh problem after Read @file Node command
Fixed several problems with the Open With command
Fixed Sort bug
Leo now updates ow/col numbers when plugins not enabled
Made dragging nodes more robust
Removed improper clones from LeoPy.leo
Removed unused fractional widths from leoFind code
updateRecentFiles now handles Null filenames properly
Code improvements
New functions in leoGlobals.py
New scripts
Rewrote dialog code
Reorganized vnodes and tnodes
Simplified startup code
Replaced exec with getattr/setattr throughout the code
Features
Added @wrap and @nowrap directives
Added apply button to Font panel
Added Icon & Status areas to Leo window
Added minimal support for Rebol and activescript languages
Added Write Dirty @file Nodes command
Changed OnIconDoubleClick to allow @url url comments
Hyperlinks are now enabled by default
Improved how headlines are highlighted
Improved startup logic so only one frame ever gets opened
Improved the reporting of characters that do not exist in the present encoding
Leo now correctly remembers the sizes of windows
Removed toggle << >> command
Undo now restores cursor
Updated Marks window in Mark Find and Mark Changes commands
Use "file in directory" format for entries in Recent Files list
Hooks
About hooks
Added "clear-mark" and "set-mark" hooks
Added hooks to customize popup menus
Added new hooks so that plugins may extend popup menus
Plugins
Added add_directives.py plugin
Added color_markup.py plugin
Added nav_buttons.py plugin
Added plugins_menu.py plugin
Added scripts_menu.py plugin
Improved how Leo loads plugins
Reorganized Leo folder: big change to cvs
Added scripts for Linux
3.11.1
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
Overview of 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
Support for unit testing
Putting the Leo icon in Leo windows
Extending the format of .leo files
Attaching temporary attributes to vnodes and tnodes
Recommended conventions for plugins
Script Find and Script Change
How it works
Using the initScriptFind script
Ideas for using scripts
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
Bugs involving paths & derived files
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
2.x releases
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
A recommended workflow for using Leo
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
@root options
@wrap and @nowrap directives control line wrapping in the body pane
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
Script Find and Script Change
How it works
Using the initScriptFind script
Ideas for using scripts
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
Checking outlines
Creating and deleting headlines
Cutting, pasting and deleting headlines
Expanding and contracting headlines
Hoisting & De-hoisting headlines
Marking headlines
Moving, sorting and reorganizing 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
Comparing files
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
Running Leo in batch mode
Chapter 8: Customizing Leo
Using leoConfig.leo and leoConfig.txt
Using plugins and hooks
Overview of 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
Support for unit testing
Putting the Leo icon in Leo windows
Extending the format of .leo files
Attaching temporary attributes to vnodes and tnodes
Recommended conventions for plugins
Script Find and Script Change
How it works
Using the initScriptFind script
Ideas for using scripts
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
Other representations of trees
Part 1
Part 2
4.0 Theory of operation
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)
@encoding
@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
Format of derived files (3.x)
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>>
Format of derived files (4.x)
Future Features
Known Bugs & Limitations
Reference for @file trees
Sherlock
Unicode
How Leo handles unicode
Unicode settings
XML file format
Summary of the 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 about Leo
What's the best way to learn to use Leo?
Where are the simple examples?
Why should I use clones?
Which should I use: @root trees or @file trees?
Unicode and special characters
Some characters in derived files look funny. What can I do?
Some characters are garbled when importing files. What can I do?
Using derived files
How do I prevent Leo from inserting sentinels in derived files?
Appendix 1 to Chapter 4: Using @rawfile, @silentfile and @nosentinlefile trees
How do I prevent Leo from expanding sections?
Appendix 1 to Chapter 4: Using @rawfile, @silentfile and @nosentinlefile trees
How do I generate files after using the Import commands?
How can I create Javascript comments?
How can I disable PHP comments?
How can I use Leo with unsuppoorted languages?
How do I make derived files start with a shebang line?
How do I use Leo to create cweb files?
About cweb mode
Can @file trees contain material not in the derived file?
Customizing Leo
Overview of plugins and hooks
I'm having trouble getting plugins to work. What should I do?
How can I add support for a new language?
Chapter 8: Customizing Leo
Using leoConfig.leo and leoConfig.txt
Using plugins and hooks
Overview of 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
Support for unit testing
Putting the Leo icon in Leo windows
Extending the format of .leo files
Attaching temporary attributes to vnodes and tnodes
Recommended conventions for plugins
Script Find and Script Change
How it works
Using the initScriptFind script
Ideas for using scripts
Importing files
How can I import many files at once?
Other topics
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?
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
Hoist & dehoist
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
Running Leo in batch mode
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
Leaps (Leo Enhancement and Advancement Proposals)
100's administering Leaps (and LeoCon)
Leap 102: LeoCon 1
200's single-user Leo
Leap 201. (abandoned) Prototype link nodes and vxnodes
Leap 202. (abandoned) Create cvs-friendly file format for derived files
Leap 203. Use Python Mega Widgets
Leap 204: (In 3.12) Add spell-checker
300's LeoN and beyond
Leap 301: Use central server
Leap 302: Produce a plugin for a Resolve CVS Conflicts command
Comments by Josef
Diary entries
2003
August 2003
8/30 Added horizontal scrollbar in body pane when @nowrap in effect
8/29 Removed Open Python Window command
8/28
Added encoding comments to leoGlobals.py and french plugins
Tested with Python 2.3
8/27 Recycle window objects only if more than one window open
July 2003
7/25 Improved Leo's FAQ
7/24
Fixed Linux install scripts
Changed default new window size in leoConfig.py to match leoConfig.txt
Added Help Wanted section to the main todo list
7/22
Called setDefaultDirectoryForNewFiles from all variants of the Save command
Added new hook for LeoN in the startup code
7/19 Fixed shortcut bug
7/18: Released 3.12 final
7/18: Added preliminary wxWindows project
7/16
Finished unit testing of colorizer
Allow minus signs in rebol keywords
7/13
Fixed double-click bug on startup
Simplified startup logic
Created new screen shot on Leo's web site
7/11
Fixed reporting bug in convertAllTabs/Blanks
Removed c.copyTree and v.copyNode and rewrote v.copyTree
Fixed bugs involving undoing entire trees.
7/10
Improved (I hope) dragging code
Added support for actionscript and rebol
7/7
Improved how headlines are edited
Created separate leoPlugins.leo
(Checked all code involving getTextSelection)
Undo/redo now restore selection and insert point
Fixed still more Open With problems
June 2003
6/30
lift dialogs when they are deiconified
Fixed another bug in createOpenWithTempFile
Fixed missing log messages bug
Changed destroyOpenWithFilesForFrame and createOpenWithTempFile
6/29
Added doc strings for most classes.
Redefined all major classes so they can be overridden by plugins
Improved spell-check plugin
6/28 3.12 beta 1 released
Rewrote distribution scripts to handle new directory organization
Fixed three bugs reported by pychecker
6/27 Rewrote callbacks in createMenuEntries
6/24 Added @wrap and @nowrap directives
6/22
Investigated new Open With problem
Added special case to idle_body_key to handle delete key
atFile.scanText no longer strips leading whitespace from headlines
Made window bigger when opening Leo from leo.py
6/21 Completed all gc cleanup tasks
6/19
Moved all window closing code into app class
Removed all destroy routines
Removed all 4.0 debris
6/18
Scanned for fractional height/width params. None remain.
Removed unused fractional widths from leoFind ctor
Set initial_window_width to 150 in leoConfig
6/16 Fixed _huge_ leak in Tk.Canvas
6/10 Implemented incremental redrawing
6/8 Resolved copying issue
6/1 Removed improper clones
May 2003
5/31
Disabled cut/paste of outlines
5/23
Added "close-frame" & "destroy-all-global-windows" hook code to destroy nav windows
Added titles to Recent and Marks dialogs.
Added "close-frame" and "destroy-all-global-windows" hooks
5/22
Rewrote the nav_buttons plugin using classes
Completed nav_buttons plugin
5/22 ** nav_buttons plugin!
Created nav_buttons plugin
use string.zfill instead of aString.zfill in color_markup plugin
5/21
Experiments with startup scripts
5/20 ** Added Icon & Status areas
Added idle handler to update the row/col indicator in the status area
Added convenience routines for icon & status areas
Disable non-functional parts of Find panel
Selected find text on entry to find panel
Added icon/status areas. REMOVED MAJOR KLUDGE
5/19
Partially cleaned up leoFind class
TO DO: Convenience routines
Removed checkForLeoCustomize & related logic
Documented new stub hooks
5/18
Added support for bolditalic and italicbold
Rewrote logic involving images.
5/17
Simplified startup code
Created the add_directives plugin
Created "scan-directives" hook in scanDirectives
Created "init-color-markup" hook
Fixed recent bug in colorizer
5/16
Improved how Leo loads plugins
Cleaned up some hooks
Created plugin to handle markup in body pane
Added stub hooks for cutomizing the drawing of the outline
Removed toggle << >> command
5/15 stub hooks
Created the plugin and script menu plugins
Breakthough: stub hooks & alphabetical order
5/13
Progress report 5/13/03: please read
Merged devel folder into main cvs folder
Updated version number to 4.0 alpha 1
Removed __init__ from files managed by LeoPy.leo
Use @ignore for all scripts
5/12
Reorganized Leo folder: big change to cvs
Investigated strange behavior of log pane during Open commands
5/4
Gathered statistics for threadNext
defined funcToMethod in leoGlobals.py
Experimented with precomputing threadNext and threadBack when linking nodes
Experimented with using v.mChildren to represent children
Changed OnIconDoubleClick to allow @url url comments
atFile code now use _permanent_ fileCommands.tnodesDict
Improved and tested checkForPossiblyBrokenLinks
Cleaned up atFile.read code
Cleaned up getVnode code
Added headString arg to tnode ctor
Moved createTopologyList to leoGlobals.py
5/3
Many changes to file reading code
Created scripts to check outline for clone & joinList errors
Moved headlines into tnodes
Second rewrite of v.joinNodeTo
5/2
updateRecentFiles does nothing on Null filenames
Major change: rewrote joinList code and move joinLists to tnodes
5/1
Fixed bug when userID != defaultID
Made lines following @+t and @+v look like sentinels
Checked that Leo 3.11 can read new .leo files
Made sure leo works without leoID.txt when use_gnx is false
Optimized how Leo allocates t.gnx
April 2003
4/20
Removed vnx fields from vnodes and +v sentinels
Put getpreferredencoding code in try: except blocks
Made sure that code works on old files when a.use_gnx is true
Replaced fileCommands.getT by self.tnodesDict.get
4/29 Transition to 4.0 .leo files
Allocate gnx's only when needed (when writing files)
4.0 code writes tnodes even if they have no body text
4.0 code writes tnx="tnx" instead of t="index" in vnodes and tnodes
Write vnx and tnx instead of gnx in vnodes and tnodes
Made use_pre_4pt0_file_formats setting functional
4/28
Use tuples instead of dicts in nodeIndices class
Added code to remove gnx id if it matches default_gnx_id
Added code to read/write default_gnx_id field in globals section
Added code to write gnx attribute of tnodes and vnodes
Added code to read gnx attribute of tnodes and vnodes
Added old and new to list of valid @file options
Added use_pre_4pt0_file_formats config option
Added Write Old Format Outline command to Export menu
4/27
Made read-only messages red
4.x code always writes @+t sentinels even for empty nodes
Created gnx's in vnode and tnode ctors
4/27 3.11 final released
4/26
Greatly simplified Plugins menu code using list comprehensions
Added 4.0 code to atFile.read that deletes all children of root
Fixed bug that prevented email line in About Leo from working
Prepared for 3.11 final: updated dates and urls
Fixed various bugs reported by Pychecker
4/24
Added support for @lineending directive
Created utility routines for scanAllDirectives and scanDirectives
Fixed bug in skip_long
4/23 Improved dialog that gets id for leoID.txt
4/22 Added getpreferredencoding to leoGlobals.py
4/21 Added patch by Davide Salomoni to OnIconDoubleClick
4/20 Removed all __del__ methods
4/5 Removed write_clone_indices & use_relative_node_indices config ivars
4/5 Added thin_at_file_trees & load_derived_files_immediately options
4/4 Added call to c.redraw() in fileCommands.readAtFileNodes
4/4 Fixed unicode bugs when Tangling and Untangling
4/3 Eliminated caught exception in frame.setTabWidth
March 2003
3/30 Don't write gnx for ending sentinels
3/30 Added code to handle afterref sentinels
3/29 New read logic passes its first test
3/28 Use scanText as a base for 4.0 read logic
3/26
Installer now creates plugins directory
Improved scrolling when moving right
Test
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
NewHeadline
3/26 Removed duplicate redraws when renaming nodes
3/25 Created frame.setWindowTitle and associated logic
3/22
3/22 Added support for body/headline_gmt_time settings
3/22 Allow color param when queuing messages to log pane
3/22 Check for valid encoding when setting app().tkEncoding
3/18
3/18 Use app().tkEncoding to convert to unicode in log pane
3/18 Tangle now supports output_newline option
3/17 Fixed bugs in promote/demote
3/8
3/8 Full recolor after paste
3/8 Put file name first in window title
3/8 Added calls to toEncodedString before many calls to write
3/7
3/7 Fixed huge performance bug in v.unjoinNode
3/7 Added statistics gathering routines to leoGlobals.py
Feb 2003
2/26 3.11b3 released
2/20 Reinstated Expand All command
2/18 Fixed auto-indent for python
2/17 Fixed minor Import @root bugs
02/17 3.11b1 released
02/11
Fixed crasher in canFindMatchingBracket
Removed odious locks on customizeLeo.py
Fixed control-V bug for Linux
02/08
Major reorganization of hooks
Fixed bug: double-clicking icon set the cursor to the hand
Fixed bug: v.OnEndDrag fires event only if dragging
02/07
replaced c.contractVnode and c.expandVnode
02/04
Fixed "% (greenOgre)" config bug
02/02
Added code to scroll partially visible headlines when they are selected
02/01
Added code to allow sorting of top-level nodes
Hacked atFile.writeAll to allow multiple writes of cloned nodes
Removed redundant code from v.set/clearDirty routines
Jan 2003
01/31
Udated recent files menu when saving a new .leo file
Prefs dialog close button equivalent to cancel
Fixed about hooks typo
New Go To First/Last/Parent/Prev Sibling/Next Sibling commands
Major improvements to expand/contract commands
1/30
Create Toggle Angle Brackets command
Created Insert Body/Headline Time/Date routines
Added "Replace tabs with spaces" checkbox in Prefs panel
Made Find button the default button in the Find panel
Allowed @comment with @language plain
1/29
Added "save1" and "save2" hooks
Added disabled support for @folder to customizeLeo.py
Added support for Show Invisible color options
Added support for look_for_control_drag_on_mouse_down option
1/25
Fixed problem with block comments
Report
Reply
1/24
Fixed problem with cutting & pasting nodes
Removed potential hang in frame.OnOpenWith
Added support for body_insertion_cursor_color setting
Fixed unicode in Tangle logic
1/23 Completed transition to unicode
1/21 Encode find/change strings as utf-8 in leoConfig.txt
1/20 Began conversion to unicode internally
1/18
Worked around True/False problems in Python 2.3 a1
1/7
Added colon to list of bad characters removed by sanitize_filename
2002
Dec 2002
12/18
Added event=event to handleLeoHook
Demoted up/down arrow stuff to a disabled hook for now.
12/17
Created tree.moveUpDown
112/16
Replaced string.x(s...) by s.x(...)
Improved es and trace, added trace_tag
Fixed crasher in undo logic
Reset the canvas cursor by brute force
Tried to prevent "stuck" drag cursor
Reviewed all calls to handleLeoHook
Fixed pychecker problems
12/14/02 3.10 released
12/12
Added "missing" event hooks
12/11
Fixed major bug in Execute Script command
12/10
Added convenience routines to redirect stderr and stdout
12/8
wrote redirectPrintToLog & restorePrintFromLog
Fixed @nsf bug reported by Travers A. Hough
Discovered bugs in undoReplace logic
12/7
Fixed undo bug
12/6
Fixed reversion involving @comment & @language together
major revision of the menu code
12/4
Fixed path problem in paths passed to tangle_done.py
Fixed Recent Files command
Fixed problems with Open With code in customizeLeo.py
Fixed crasher in colorizeAnyLanguage
Fixed major memory leak
Changed shortcut to Find Previous so Alt - F4 closes Leo
Changed url of help file
Removed backquotes from most print statements
Removed synonyms from leoImport.py and leoTangle.py
12/3
Converted leoConfig.py to use @root-code
Added @file & @root options
Fixed a bug in Untangle
Decided not to do utils_normpath
Nov 2002
11/25
Improved Leo's window icon
cleaned up config code relating to find settings
Added Node only option to find menu
Added Export Headlines command
11/21
Fixed bug in colorizeAnyLine
Improved undo for Edit Body commands
11/20
Fixed unicode bugs
11/19
Changed use_configureLeo_dot_py to use_customizeLeo_dot_py
Create all non-existent directories using makeAllNonExistentDirectories
Made sure all open calls are in try blocks.
Recolor entire pane if @color or @nocolor change
Changed the lamda used to define the Recent Files callback
Added "openwith", "recentfiles" and "@url" hooks
Fixed big leak in undo
Properly save and restore selection range for undo.
Fixed bug that caused a backspace in an empty body text to insert a newline.
Made Undo bulletproof
Removed code in tree.select that removed trailing newlines
11/18
Added Open with entries to popup menus
Added David McNab's auto-indent patch (smart_auto_indent option)
11/17
Added "headkey1" and "headkey2" hooks
11/16
added enableIdleTimeHook, disableIdleTimeHook, idleTimeHookHandler
Completed first draft of Go To Line Number command
11/15
Added Go To Line Number command
Rewrote syntax coloring for cweb
11/14
Fixed bug in atFile.scanAllDirectives involving @language
11/13
Added keywords parameter to customizeLeo
Implemented the use_configureLeo_dot_py option
Released first public draft of incremental undo
11/10
Correctly colors Pascal's "private" and "uses" keywords
11/9
Fixed blunder in undo/redraw code
Fixed Dave Hein's @language issue
Completed incremental syntax coloring
11/8
Worked on incremental syntax coloring
Changed leoCustomize.py to customizeLeo.py to make distribution easier.
Warn if @node leoCustomize.py is not @ignored
Execute script no longer supports @pythonscript
Added example of how to customize individual .leo files safely in leoCustomize.py
Added frame.longFileName and shortFileName methods
11/7
Prompt when writing leoCustomize.py
Disabled the "virus-word" in leCustomize.py
11/6
Fixed a major bug in top()
top
Implemented per-window hook scheme
11/5
Fixed bug in cweb coloring, made latex work more like cweb
Added app().realMenuName logic
Added "open1", "open2", "menu1" and "menu2" hooks
Completed generalization of menu code
11/4
Replaced all frame menu ivars with frame.menus dict
Generalized how commands are called
Added general-purpose hook handling
Use original leading whitespace when reformatting paragraphs
Use scanDirectives in Convert All Blanks/Tabs commands
Incorporated Dave Hein's recent changes
11/3
Implemented @pythonscript and Execute Script
11/2
Fixed bugs when importing Java files.
Fixed important bug in find_line_start
1/11
Fixed cweb hack using Jonathon Gilligan's patches
(reducing name polution)
Oct 2002
10/30
Applied cweb hack to all sentinels
raw cweb mode in @root trees
simplified Reformat Paragraph command
10/29 Released 3.8
10/28
Don't recognize @*, @< or @( in cweb mode
Completed documentation for 3.8
10/27
Fixed problems report by Pychecker
Fixed path bugs in tangle.scanAllDirectives
Changed path_directive_creates_directories to create_nonexistent_directories
10/26
Fixed foo.leo bug
Worked around Control-T bug
Reset hand cursor after failed drag
10/25
Used dicts to speed up checkMoveWithParentWithWarning
Created reloadAll in leoGlobals.py
Allow noweb section refs in @file cweb code
Added support for <?xml-stylesheet...?> line
Documented @rawfile and @silentfile
10/24
Fixed cweb sentinel bug
10/23
Completed first draft of new @rawfile and @silentfile
Discovered that gti's _are_ possible
Finished cweb hack
Fixed @delims "round trip" bug
Fixed bug involving menu pastes in headlines
10/19
Fixed import to @root problems
10/18
Completed handling of output_initial_comment option
Fixed block-comment bug in Java and HTML
Use // as single-line comment in Java
10/17
Allow @comment and @language to coexist
Match routines all return false if pattern is None or empty
Colorizer now recognizes comments after preprocessor directives
Corrected reversion in set_delims_from_string
10/16
Added support for forth language comments
Fixed another crasher in scanAllDirectives
Fixed assert failure in putBodyPart
Fixed crasher in scanAllDirectives
10/15
Added @raw support
Changed names of commands in help menu and corresponding entries in leoConfig.leo
Updated docs for recent features
10/14
All export commands now use output_newline option
Forced focus to body pane after ending headline edit
wrote "finished" after write @file nodes command
Use @@ sentinels only for real Leo directives
10/13 Rewrote colorizer using dispatch dict
10/12
Added Configuration Options item to help menu
10/11
added code to support output_newline option
Fixed reversion in file write code
Greatly simplified leoConfig.py
Completed support for @quiet
Syntax color perl's alarm function
10/10
Removed language bits
Removed directive bits
Used @q and @> as sentinels in cweb mode
10/9 (after 3.7 released)
fixed Unicode bug in setBodyStringOrPane
Created es_exception
Added support for @rawfile (fixed bug in endEditLabel)
10/09 Released 3.7
10/8 Ran pychecker
10/7 Improved error messages for Unicode errors
10/6 leoOpen sets file name if it doesn't exist
10/5 Leo now closes an empty window when opening a recent file
10/4
Improved handling of Latin-1 encoding
Initialize all "config" section params to valid values
10/3
Removed all calls to decode
Got information about encode/decode
From Martin v. Löwis (use unicode to interface with tk)
Fixed Find previous bug
Retained file modes when tangling and writing derived files
Added remove_sentinels_extension option
10/2 Changed find to findr when scanning @@ sentinels
Sept 2002
9/27
Removed extra newline following @+leo directive
9/26
"Relative" node indices aren't
Eliminated writing clone indices!
Allowed reads and writes of cloned @file nodes
9/25
Added perlpod hack
Added getBaseDirectory and used it to resolve relative paths
Removed bodyKeepsFocus logic in leoTree.py
Added code to various scanAllDirectives routines to create paths
Created "path_directive_creates_directories" option
9/24
"Mysterious" changes to files were due to setting Page Width = 80
Added code to configure headline colors
Made the About Leo dialog non-modal
Fixed bug that disabled all shortcuts
Added support for Esc in shortcuts
Added End Edit Headline and Abort Edit Headline commands
Fixed undo cloned headline bug
9/23 Finished user-configurable shortcuts
9/21
Fixed colorizing for "while" in C
Leo now remembers insert point always
9/20 Fixed tab problems
9/19 Removed setup.py from binary distribution
9/18 New file code: end of cursed newlines.
Write only \n (Linux newlines) in derived files and .leo files
Major change to atFile::read code
9/15
Added self=self to lambda definitions to keep Python 2.1 happy
Folded all CVS changes into this file
9/14 Added secondary pane ratios to leoConfig.txt
9/13
Folded Steven P. Schaefer's @first in @root code.
Fixed "Unselected Ctrl-[ four times" bug
Investigated reported font settings bug: works for me
Incorporated Dave Heins code into getBodyLines
9/12
Fixed bug in updateBodyPane affecting Edit Body commands
Suggested ISO-8859-15 instead of ISO-8859-1 in leoConfig.leo
Generated PKG-INFO in Version Stuff section
Fixed @language plain # bug.
Improved enabling of menu items in popup menus
Fixed @language plain single-quote bug
Incorporated Dave Hein's "wrapping in doc part" bug fix
Changed accelerator of Go To Next Changed to Alt-D
9/11 Released 3.6
9/11 Hacked colorizeAnyLanguage to support all PHP constructs
9/11 Implemented @last
9/9
Removed most error messages in atFile::read logic
Integrated changes in PHP changes into this file
9/7
9/7 and later: started to design the "Resolve CVS Conflicts command"
Dave Hein: added new code to make Python window work on Linux.
9/5 Fixed bugs in @path logic in tangle.scanAllDirectives
9/4
Tomaz Ficko: Added support for MouseWheel (Linux only)
Steven P. Schaefer: Fixed cursed newline bug for Linux
9/3
Experimented with CVS Revision keyword in About box.
Added Match Brackets command
Added shortcuts for Read/Write @file Nodes commands
9/2
Steven P. Schaefer: Improved handling of directives in @root trees
Dave Hein: added CheckVersion to leoUtils.py
Dave Hein: fixed Tk 8.4 problem in Prefs Panel
Body pane now gets focus after Go Back/Next
Changed leoTangle.scanAllDirectives
Leo now remembers scrollbar positions
Removed windows newlines in leoConfig.update
Improved call to os.path.join in leoApp.finishCreate
9/1
Saved edit position of tnodes
Investigated @language for @root trees
Fixed missing headline bug on startup
Changed all os.rename to utils_rename
Changed mode in leoConfig::update
Aug 2002
8/24
Added version to url for online tutorial
Improved About Leo dialog
Improved dowloading of sbooks.chm
8/15
prototyped window icons in Tk
Attempted to make mouse wheel functional in tree pane
Rewrote About Leo dialog with icon & hyperlinks
Improved download dialog in Help-Tutorial command
8/14 Released 3.5
Corrected wrapping in log window when horizontal scrollbar is present
Released 3.5
Added log_pane_wraps and outline_pane_scrolls_horizontally params
Fixed Tangle logic
8/13 Bugs, bugs, bugs
called cf.flush in leoConfig.update
Open url's from help menu
Rewrote path logic
Warn on read-only files
Hacked on leoAtFile.scanAllDirectives to fix directory problem
Fixed (?) cursed newline bug in leoAtFile
8/11 Released 3.4
Fixed crasher when leoConfig.txt does not exist
Fixed bug in leoFileCommands.readOutlineOnly
Wrote importFiles script in leoImport.leo
Released 3.4
Syntax colorer now honors @comment
8/10
Added save_clears_undo_buffer configuration option
Improved Recent Files menu
8/9 Fixed Unicode Bugs
8/6
Corrected problems with find/prefs settings
created leoConfig.leo
Fixed a bug in config.setDict (!) (This fixes font problems)
Leo writes split direction to leoConfig.txt
Leo writes prefs if leoConfig.txt is read-only
8/5 Leo honors pane ratio stored in .leo files
8/4
Improved highlighting of whitespace
Fixed revert problems in font panel
Added @ignore to "raw" imported files
Remove Sentinels command now uses .txt extension instead of .tmp
Added log message for Remove Sentinels command
Corrected spelling of limit_directory_search_extenstion
Fixed wandering insertion point bug
8/3
looked for backticks
Cleaned up files using nested compares
Fixed another clone bug: created setAncestorsOfClonedNodesInTreeDirty
Released 3.3
8/2
Leo supports Unicode !!
changed skip_long to handle negative values
initialized atFile.default_directory to None
eliminated both presentLanguage and targetLanguage ivars from leoAtFile
Removed default param from set_language
8/1
Fixed crasher in leoTangle.putDoc
Fixed problems with negative tab widths
Fixed the "delims botch"
Made 'additional_body_text_border' setting functional
Made 'body_pane_wraps' setting functional
Fixed config tables
Initialized leoAtFile properly
July 2002
7/30 Fixed many bugs in Color panel
7/30 Proved that Leo handles nested @others directives properly
Proof that nested @others directives are valid
7/29
7/29 Fixed bug in skip_pp_if and added skip_pp_part
7/29 Added @ignore to the root of all imported trees
7/29 Fixed bug in scanPythonDef.<< skip the Python def >>
7/29 Improved import of classes
7/29 Relaxed the rules for @others: allowed @others within @others tree
7/29 Added glossary entries
7/29 Fixed crasher in Prefs onCancel
7/29 Fixed problems with Outline/Move/Select Go commands
7/29 Added email address & URL to About Leo dialog
7/29 Fixed import bug
7/27 Continued comments for lines ending in \
7/24 Added @file disStats.py
7/24 Added leo.profile()
7/20 Released 3.1
7/20 Finished Compare panel code, including using leoConfig.txt params
7/19
7/19 Leo now adds path to Idle automatically
7/19 Fixed bug that caused leo.py not to run
7/19 Completed the visual design of the Compare panel
7/18
7/18 Revised leoCompare.py
7/18 Changed precedence scheme for preferences
7/18 Added support for @language tcl/tk
7/18 Fixed major bugs in skip_long & tangle.scanAllDirectives!
7/17
7/17 v.moreBody now only escapes first non-blank character on a line
7/17 Told where to download Python 2.2 and Tk 8.3 In 2.2 required dialog
7/17 Added optional text param to leoDialog.askOk
7/17 Tried closing Leo window during a long write
7/17 The compare window is important!
7/16 Released 3.0
7/16 Added leoConfig.txt to manifest
7/16 Did spellcheck on LeoDocs.leo using MS Word
7/16 Fixed autoscrolling so it can stop
7/16 Ran pychecker
7/16 Called traceback.print_exc for all serious exceptions
7/15 Leo code complete
7/15 Adjusted tree to handle larger font size
7/15 Make Convert All Blanks and Convert All Tabs undoable
7/15 ** Speeded up autoscrolling enough to be useful
7/15 New windows now open using config params.
7/15 Cleaned up how windows are opened
7/15 Fixed bug that disabled window positioning!
7/15 Changed config getters to get from defaults
7/15 Added code to support font prefs.
7/15 Added frame code to support non-modal panes
Earlier
7/14
7/13 Added default tables to config module
7/13 Added name picker to color panel
7/13 Finished handlers for Font frame
7/12
7/12 Font & color panels need not be modal !
7/12 Finished Tk work on Font frame
7/11
7/11 Fixed syntax coloring bug
7/11 Lots of work on preferences and color panel
7/10 File (and clipboard) format changed!
7/10 Allowed "ISO-8859-1" specification
7/10 Warn and abort if not Python 2.2 or above
7/10 app.finishCreate checks for Python 2.2 or higher
7/10 Cleaned up Recent Files menu code
7/10 All panels now have labeled frames and grooved borders
7/10 Created leoFontPanel.py and added leoColorPanel class to leoColor.py
7/9 Created create_labeled_frame and Font and Colors panels
7/8
7/8 Improved syntax coloring
7/8 The REM hack & fixed crasher
7/8 Fixed crasher in atFile.scanAllDirectives
7/8 Finished Toggle Split Direction
leo.py 2.5 and earlier
7/7
7/7 Fixed crashers when undoing or redoing moves
7/7 Updated LeoDocs.leo and Leo's web site
7/7 Released 2.5 and updated CVS
7/6 version 2.5 is now code complete
7/6 Improved Import to @root
7/6 Fixed Import bug: empty classes and nested classes
7/6 Fixed Import bug that caused orphan nodes
7/6 Fixed Import bug that caused duplicate class lines.
7/6 Renamed Font to Set Font... and Syntax Coloring... to Set Colors...
7/6 Created c.initJoinedCloneBits
7/6 Verified that undo works
7/5
7/5 Made extract commands undoable & simplified undo.
7/5 Made import commands undoable.
7/5 Discovered traceback module
7/5 Made Cascade command functional
7/5 Enabled Minimize All command
7/5 Forced syntax coloring after extract commands
7/4
7/4 Find/Change settings can be overridden by leoConfig.txt
7/4 Invalid directories are no longer errors in leoAtFiles.scanAllDirectives
7/4 Prefs now can be overridden by leoConfig.txt
7/3
7/3 Added Convert All Blanks and Convert All Tabs commands
7/3 Added Convert Tabs command
7/3 Changed tabs to blanks when tab width is negative
7/3 Changed c.tab/page_width to self.tab/page_width in leoAtFile.py
7/3 Added OK, Cancel, Revert buttons to Prefs panel
7/3 Added window name to Prefs title
7/3 Added @language python to all @file nodes
7/2
7/2 Changed defaultextension from "x" to ".x"
7/2 Made prefs a modal dialog
7/2 About c.defaultDirectory
7/2 Changed Save and Save As...
7/2 Fixed crashers in Remove Sentinels command
7/2 Fixed crasher in import.scanAllDirectives)
7/2 Hacked code so tab width works
7/2 Fixed doc problems
Demote docs
Open/Close log window docs
Insert Headline docs
Extract Section docs
Syntax Coloring... & Font...
7/1 Cut/Paste now works properly
June 2002
06/22 Implemented Recent Files menu & leoConfig.py
06/19 Removed assert from st_enter_root_name
06/18 Improved error recovery when writing .leo files
06/16 Changed leo.py so Leo can open files when files are double-clicked
06/16 Finished updating LeoDocs.leo to match Leo's web site
06/15 Fixed the control-I and control-H bugs
06/15 Double clicking in a headline now selects a word
06/15 Implemented Drag and Drop
06/04 Changed atFile.scanAllDirectives to use c.openDirectory as default
06/03 Changed cursor to double arrow when cursor is over splitter bars
06/02 Bug fix in es in leoGlobals
06/02 Tried and failed to call wm_iconbitmap
May 2002
5/30/02 Move theory of operation sections back into this outline
5/27/02 Undo move down doesn't always work
5/27/02 Fixed bugs involving changing roots
5/27/02 Handled unicode error in leoFileCommands::put
5/13/02 LeoPy.leo corrupted
April 2002
4/27/02 Started Remove Sentinels command
4/27/02 Changes to Export Noweb command
4/25/02 Added code to os()
4/7/02 Added frame.outlineToNowebDefaultFileName ivar
4/7/02 Replaced <<*>>= with <<fileName>>= whenever possible
4/7/02 Added code to make @ defs not start a doc part
4/7/02 Added warnings for all non-undoable import commands
March 2002
3/16 Fixed moves up to root postion
3/16 Simplified syntax colorer for docState
3/10 Rewrote skipLeadingComments
3/08 Modified colorer for CWEB
3/07 Color strings green
3/07 Rewrote importWebCommand and its allies
3/06 Import/Export commands
3/06 Outline To Noweb works (as well as it is going to)
3/06 Flatten Outline and Import Flattened Outline work
3/06 Python, C, Java and Pascal Imports now work
3/05 Import commands, colorizer, clear IDLE breakpoints
3/05 Import commands, colorizer
3/05 Added ability to clear breakpoints in IDLE
3/04 Released leo.py 2.0 and Borland v2.6
3/04 Improved read error messages.
3/04 Untangle calls v.trimTrailingLines only for changed nodes
3/04 changed v.setBodyStringOrPane
3/04 Added finished messages for Tangle/Untangle
3/03 Fixed Untangle,
3/03 Created readme files
3/03 Fixed Untangle bug
3/02 Finished Undo, Preparing for release
3/02 Created distutils files to distribute Borland Leo
3/02 Created release notes for Borland v2.6
3/02 Documented new commands and features
3/02 Fixed all pychecker complaints against leoImport.py
3/02 Added @space to Utils & Tangle/Untangle code in LeoCB
3/02 Completed Undo Change All
3/01 Finished Tangle, Finished v2.6, etc.
3/01 Completely supress all auto-indent in @nocolor mode
3/01 Tangle now works exactly like LeoCB Tangle
3/01 Finished Borland version 2.6
Feb 2002
2/28 Small fixes
Tangle now treats @c like @code
Changed is_special_bits
Installed Johansson patches
2/27 pychecker(!), colorizer bug
2/27 Fixed bug in colorizer
2/27 Discovered pychecker (several bug fixes)
Fixed bug in Mark Changed Roots
Fixed bug in scanText related to @delims sentinel
Fixed bug in write
Fixed bugs in leoFileCommands
Fixed bugs in leoNodes
Fixed bugs in leoTangle
2/26 LeoCB can read compressed derived files
2/25 Fixed IDLE, added verbatimAfterRef sentinel
2/25 Fixed breakpoints in IDLE
2/25 Added verbatimAfterRef sentinel
2/24 suppressing newlines
2/23 Untangle, colorizer bug
2/23 Debugging untangle: fixed leading single-comments bug
2/23 Fixed small colorizing bug
2/22 askOKCancel dialog, suppressing auto-indent.
2/22 Created askOkCancel dialog for Read @file Nodes
2/22 Supressed auto-indent in @nocolor mode.
2/20 and 2/21 Debugged Untangle
2/19 Improved Unlimited Undo
2/18 Unlimited Undo works
2/16 Syntax coloring
2/16 Fixed bugs in colorAnyLanguage
2/16 Finished adding keywords for colorAnyLanguage
2/15 Syntax coloring, undo, @space bug
02/15 Created colorAnyLanguage
02/15 Fixed @space bug
02/15 Bug fix: section definition lines now end coloring for doc parts
02/15 Finished Limited undo.
02/15 Added files to manifest
02/14 CVS
02/14 Fixed Undo problems
02/14 Checked out Leo from CVS
02/14 Cant mend clones, How to use CVS
CVS: Making a virtue out of necessity
Ooops: Leo can't mend clone links!
Completed Mending dialog
02/13 Mending clone links, undo
Made the undo class partly functional
Created menu utilities
Fixed bug in Extract and Extract Section commands
02/12 Minor fixes
Studied the DnD code
Cleared tab and space tags in colorizePython
Made headlines longer
Cleaned code
02/11 experiments
created sortSequence utility
experimented with inspect
02/10 1.0 released
Removed "from x import *" from reload_all() and OnOpenPythonWindow'
Released 1.0
Added Contract Parent (Alt-0)
Fixed bugs in Prefs panel
02/09 Preparing for release
02/09 Got clean import of leoImport.py
02/09 Created do-nothing leoUndo.py
02/09 Updated current language properly in Prefs panel
02/09 Re-marked @file nodes dirty on write errors
02/09 Prefs Panel stick to current window
02/09 Tangle passes full regression test
02/09 Added Sort Siblings
02/08 Regression testing
02/08 Found Tangle problem
02/08 Cleaned up leoCompare.py
02/08 @c in Tangled files
02/08 Testing wiped out files!
02/07 New commands and bug fixes
02/07 Possible reversion
02/07 Converted Import Files code
02/07 Search suboutline only now applies to Search/ChangeAll
02/07 Fixed bug in search()
02/07 Fixed bug: @others wasn't colored unless at left margin.
02/07 * Added indent and undent commands
02/07 * Improved idle_body_key
02/06 Finished commands
02/06 Rewrote and tested all Edit Body commands
02/06 Rewrote and tested Convert Blanks command
02/06 Removed Print and Page Setup commands
02/06 * Implemented View All Characters
02/06 Got the arrow binding right
02/06 finished Delete command
02/06 created tree.onBodyChanged
02/06 rewrote and tested Revert
02/06 created leoDialog.yesNo
2/05 More bug fixes
02/05 * Extended Sherlock
02/05 * Properly enabled and disabled all menu items
02/05 Got special accelerator keys working
02/05 GoTo commands now working
02/05 Got Alt-keys working
02/05 Made es and trace safer during quit
02/05 Fixed bugs in Search Headline and Find, Then Change
02/05 Do not eliminate begin/endUpdate!
02/04 Many bug fixes & some big improvements
02/04 Fixed Change command in headline
02/04 A kludgy way to cut & paste between Leo & Leo.py
02/04 Ended all event handlers with return "break"
02/04 Changed Insert Node accelerator from Control-K to Control-I
02/04 Implemented Toggle Active Pane
02/04 ** Overrode default text keys
02/04 Documented control characters used in the body pane.
02/04 ** Breakthrough regarding events and accelerators
02/04 Implemented Select All command
02/04 created OnBodyDoubleClick and allies
02/04 create setTextSelelection utility
02/04 Created Sort command
02/04 Fixed Tangle Marked and Tangle All commands
02/03 Created shortcuts for opening LeoPy.leo
02/03 Released 0.08
02/02 Fixing Bugs
02/02 Fixed bugs in outline-only and whole-word searches & is_c_id
02/02 ** Drawing breakthroughs
02/02 Fixed control-d bug
02/02 ** About redrawing
02/01 Fixing bugs
02/01 Test tangle path errors
02/01 ** started tree.makeVisible & tree.scrollTo
02/01 ** used distutils
Jan 2002
01/31 Preparing for release...
** 01/31 leo.py now tangles better than LeoCB
01/31 Created open script in leo.py
01/31 Fixed suboutline only logic
01/31 Tested directory logic
01/31 replaced frame.defaultDirectory by c.tangle_directory
01/30 Major improvements...
01/30 ** May have solved the load path problem
01/30 ** Rewrote path logic: removed all calls to os.chdir
01/30 ** Fixed major bug causing @file read errors
01/30 ** Changed how directories are handled
01/29 Improved Paste Node command
01/28 Generalized trace to allow function args
01/28 Found source of Find performance bug
01/25 Found one source of misreads
01/24 Implemented Sherlock: init_trace and trace
01/22 More work on leoTangle.py
01/21 Started workon leoTangle.py
01/20
01/20 Lost clones into leoColor.py
01/20 Implemented hyperlinks
01/18 Completed Change all command
01/17
01/17 Breakthrough: tree.redraw_now
01/17 much work on Find class
01/17 v.edit_text defined only if v is visible
01/16
01/16 Fixed idle_headline logic
01/16 Lost data in outline (not derived files)
01/16 Fixed minor find/change bugs and removed batch checkbox
01/16 Breakthrough re event handlers!
01/15 Find/Change
01/15 ** Completed find/change commands
01/15 Converted headline from Tk.Entry to Tk.Text
01/14
01/14 Fixed bug: cancelling dialog wrote .leo
01/14 Got basic Find commands to work
01/14 Fixed Save To bug
01/14 Added log messages for saves
01/14 Always recompute file indices on saves
01/14 Reduced width of log window
01/13
01/13 exec statement simplifies code
01/13 Prefs/Find settings now functional
01/11
01/11 Replaced globals with app() ivars
01/11 New memory management: created c/v/tree/frame destroy routines
01/11 About destroy routines: eliminating circular references
01/11 disabled c.setChanged while loading
01/11 Eliminated ?? error message on exit
01/11 created leoFrame.__del__
01/10
01/10 Worked on Quit error messages
01/10 Released v 0.07
01/10 Tested c2py on Borland
01/09
01/09 Wrote and debugged leo1to2 scripts !
01/09 Made Python window functional !
01/09 Added auto indent
01/08
01/08 bodyKeepsFocus logic
01/08 Fixed bug: no doesn't abort a Quit command
01/08 Fixed bug: changing clones didn't mark joined dirty.
01/08 Got cut/copy/paste working
01/08 Fixed reooloring problem
01/08 Added Help-Leo Documentation command
01/08 Save commands now ensure .leo extension
01/08 Still no joy with canvas border
01/08 Expermented with window icons
01/08 Increased size of open windows
01/08 Improved look of prefs panel
01/08 Made progress towards "Show invisibles"
01/08 @ignore now colored properly
01/08 @color and @nocolor are now inherited
01/07
01/07 Fixed some bugs in Move Up and Move Down commands
01/07 Fixed loss-of-data on insert command
01/07 tab width
01/06
01/06 borders again
01/06 editing and selecting
2001
Dec 2001
12/20/01 Fixed Linux problems
12/19/01 Improved LeoDocs.leo
12/18/01 Improved Leo2 documentation
12/17/01 ** Leo 0.05 released
12/16/01 Found the source of the file mismatches
12/16/01 Created directory compare routines
12/15/01 Leo is operational
12/15/01 Fixed bug in scanText
12/15/01 Fixed bug in putBodyPart
12/15/01 Fixed lookahead code in scanText, scanDoc
12/14/01 Created leoCompare.py
12/14/01 Create leoDialog.py
12/14/01 Fixed minor problems
12/13/01 Fixed several clone bugs
12/13/01 Aha: we can eliminate v.setIcon !
12/13/01 ** Fixed bottleneck with endupdate(flag)
12/13/01 ** Syntax coloring now works
12/12/01 Cut/copy/paste of nodes now done using internal clipboard
12/12/01 Fixed insert, move, clone and delete commands
12/12/01 Looked into Tk & Python clipboard commands
12/11/01 Menu accelerators
12/11/01 Properly initialized the file in getLeoFile
12/10/01 Got accellerators working
12/10/01 Simplified icon handling, tree.redraw
12/10/01 Used idle-time handler to work around event problems
12/09/01 Problems with canvas border
12/09/01 Tree now drawn properly
12/08/01 Progress with selection
12/07/01 Small improvements
12/07/01 Tree now contains labels for editing
12/06/01 Open/Close events now handled properly
12/06/01 Tree drawn correctly, slowly
12/04/01 Create .gif icons
12/04/01 replaced dvnodes with vnodes!
12/04/01 read code is now fast enough!
12/04/01 tree starts to work
12/01/01
Nov 2001
11/15/01 Created do-nothing tk app using Tkinter
11/15/01 added -padx to log window
11/13/01 Speeding up reads
11/11/01 Design of tree class
11/10/01 began conversion to tk
11/10/01 es outputs newline
11/10/01 fixed bug in putDocPart
11/10/01 added code for @delims
11/10/01 got @first working
11/09/01 moved all globals into class
11/09/01 fixed bugs in write logic
11/09/01 fixed bugs read logic
11/06/01 created Leo window using Tk
Oct 2001
11/01/01 Write code works except for comments
10/31/01 Added Read/write submenu
10/31/01 Fixed write code
10/26/01 Read code is slow
10/26/01 Read/write code now works
10/11/01 Outline commands now work
10/11/01 Removed redundant getters
10/10/01 Debugged Commands, nodes, fileCommands
10/08/01 design of leoFileCommands
10/07/01 removed info and tagList classes
10/05/01 c2py fixes, speedTest
10/02/01 through 1/10/4
10/01/01 fxed bugs in c2py
10/01/01 c2py in leo
Sept 2001
9/30/01 documented c2py
9/29/01 c2py.py
9/25/01 Idle has trees & syntax coloring
9/14/01
Count pages
The heart of the file format is the <vnodes> element containing possibly nested <v> elements, and the <tnodes> element containing a list of (non-nested) <t> elements. <v> elements refer to <t> elements using "t" attributes in <v> elements corresponding to "tx" attributes in <t> elements.

<v> and <t> elements in .leo files correspond directly to vnodes and tnodes in memory when Leo is running. For full information about these see @file leoNodes.py in LeoPy.leo. For full information about exactly what Leo is doing when reading and writing files, see @file leoFileCommands.py in LeoPy.leo.

New in 4.0: <v> elements corresponding to @file nodes now contain tnodeList attributes that are a list of gnx's. The tnodeList attribute allows the read code in @file leoAtFile.py to recreate the order in which nodes should appear in the outline. The tnodeList attribute is a list of gnx's: global node indices. See the nodeIndices class in leoNodes.py for details. A gnx has the form id.timestamp.n where .n is elided unless two nodes would have the same id.timestamp field.

The following sections of Users Guide:Customizing Leo contain new conventions that allow plugins to extend this format by adding attributes to <v> and <t> elments:

- Extending the format of .leo files - Attaching temporary attributes to vnodes and tnodes - Recommended conventions for plugins
@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 have made generous donations to the Leo project: Robert Low, Nic Cave-Lynch.

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, Phil Straus, 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. Bernhard Mulder proposed a new way of untangling derived files. Paul Paterson wrote the first implementation of plugins and the spell checking plugin. 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:
I've written documentation in WordPerfert, Ventura, Word, PageMaker, and FrameMaker and even though they create wonderfully looking and useful documents, they've never been able to do what I've been looking for. HTML, compiled help files, and later PDF came closer, but still not there. I thing I've found it in LEO, a way to make a "living" document. A document built out of discrete parts that can be re-organized on the fly to meet the needs of a varying audience.

I'll be honest is saying that it will be a long road to convince management to release LEO files as public documentation, but for all internal projects, nothing but LEO from here on out. I've already started converting the IT Procedures manual from Open Office to LEO because I know it's going to be much more useful to me and anyone else. Being a convert for less than a day I've still got a lot to learn, but just the possibility of keeping system maintenance scripts in the IT manual is mind boggling.
wstapp@earthlink.net

I've just perused your program, manual and tutorials and though I have thus far only an incomplete idea of its capabilities, it contains some of the most clever features I've ever seen: clones of nodes for example. This program must have cost you a great deal of effort, not at all diminished I'm sure, by the "clearsightedness" that unfailingly shows through. I am sure that all who have examined your program admire it and that all who use it do so with a sense of gratitude. I know I do. My fields are linguistics and literature and what programming I do does not go beyond these areas, but I have programmed in C, C++, Java and Visual Basic and used a number of editors, most of which lag far behind the programming language. Thank you for making it available.
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.

New in version 4.1: The first time you start Leo, Leo will ask you for a unique identifier. If you are using cvs, use your cvs login name. Otherwise your initials will do.

Leo stores this identifier in leoID.txt, so you can change this identifier at any time by changing leoID.txt.
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/

See "How to install Leo on MacOS Panther (10.3)" for installation instructions for MacOs X. 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 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.
New in Leo 4.0 and Python 2.3: Leo will draw a Leo icon in all Leo windows. There is no need to install PIL or tkIcon.

Otherwise (for versions of Leo before 4.0 and for versions of Python before 2.3) Leo will draw a Leo icon in Leo windows only if 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 4.1 alpha 1 November 3, 2003

*** Alpha quality code: Please make full backups before playing with this code!

This release marks another significant milestone in Leo's history:

- Leo's 4.x file code is complete. At present I have no plans to change the format of .leo files or derived files. I am eating my own dog food: I do all my editing with the 4.1 code base.

- Leo uses immutable gnx's (id:timestamp:n) by default to associate tnodes with vnodes in .leo files. This makes Leo as cvs friendly as possible. From now on .leo files will be checked in to cvs with the -ko (text/keywords off) option.

- The use_gnx setting in leoConfig.txt determines whether uses gnx's (cvs friendly) or ints. No version of Leo before 4.1 can read .leo files containing gnx. Only gnx's are immutable. Leo recomputes all non-gnx indices from scratch whenever writing a .leo file. It is possible to convert between 3.x and 4.1 file formats by changing the use_gnx setting.

- The 4.1 code base has been reorganized to support gui's other than tkinter. Leo's src directory contains several new source files.

- The file test.leo in the test directory contains real regression tests for syntax coloring. Regression testing scripts create regression tests dynamically from data in Leo's outline. Very cool, very easy, very general. In particular, regression tests may use temporary nodes in test.leo rather than creating separate Tk windows for testing.

Known bugs:

- The Extract commands eat one character too much.

- The __wx_gui.py plugin is only partly functional.
Leo 4.0.4 October 29, 2003

- Fixed a problem with the titles in Font and Color panels. - Worked around a Unicode assert failure in the colorizer. - Fixed crasher in OnEndEditHeadline. - Fixed import problems in leoPlugins.leo. - @first and @last sentinels no longer contain corresponding text. This fixes a problem that affected PHP comment delimiters.

Leo 4.0.3 October 25, 2003

- Fixes several problems with the auto_save and plugin menu plugins. - Fixes a problem with the Go To Line Number command.

Note: This version does _not_ support Unicode file names or directory names.

Leo 4.0.2 October 23, 2003

- Fixes a crasher in reportBadChars when a .leo file contains a character that can not be represented in the encoding specified in the first line of the .leo file. - Leo now sends full tracebacks to the log window when Leo takes an unexpected exception. - Leo now issues a more informative message when the @comment directive inhibits the Untangle command.

Leo 4.0.1 October 22, 2003

- Fixes several crashers, one of which may prevent Leo from starting. - Fixes indentation for section references that are preceded by something other than whitespace. - Removes debugging traces when reading and writing unknown attributes. - Removes some improperly cloned nodes from LeoDocs.leo. - Adds the checkTopologyOfAllClones script. - Fixes some problems with plugins.
Leo 4.0 final October 17, 2003

More than a year in the making, Leo 4.0 is finally here.

Highlights of 4.0 final: -----------------------

** Improved and simplified format of derived files. - Eliminated child indices, extraneous blank lines and @body sentinels. - Eliminated @node sentinels that indicate outline structure. - New @nl and @nonl sentinels indicate where newlines are and aren't. - These changes will largely eliminate unwanted cvs conflicts.

** Greatly improved error handling. - Reading derived files _never_ alter outline structure or links. - Read errors leave the outline completely unchanged. - Broken clone links are gone forever. - As a result, 4.0 is much safer than all previous versions.

** Full compatibility will previous versions of Leo. - Leo 4.0 reads all derived files properly, regardless of version. - Leo writes new-format derived files by default, and this default may be changed. - New commands in the read/write menu allow you to explicitly specify the format of derived files.

* New commands: - Write 3.x Derived File and Write 4.x Derived File. - Import Derived File. - Clear Recent Files.

* Dozens of other improvements, including: - Better Unicode support. - New configuration settings. - Several new plugins.

See the notes for the various beta releases for full details.
Leo 4.0 beta 4 October 13, 2003

This is the third public release of Leo 4.0. There are no known serious bugs in this version of Leo. This will be the last beta release before 4.0 final.

Changes made in 4.0 beta 4 --------------------------

- Changed all instances of string.letters to string.ascii_letters. string.letters can cause crashes in some locales (Unicode environments). - Fixed several bugs that caused clone marks not to be set properly.
Leo 4.0 beta 3 October 9, 2003

This is the third public release of Leo 4.0. There are no known serious bugs in this version of Leo.

Highlights of 4.0 beta 3 ------------------------

- Fixed problems with setting Leo's window icons. - Plugins may now save their own information in <v> and <t> elements in .leo files. - The usual assortment of bug fixes and minor improvements.

See the children of this node for full details.
This was a long-standing bug. It's effect was cosmetic, and it was important to fix.
This could happen when the derived file was corrupted.
Leo can now read and write "foreign" attributes in <v> and <t> elements in .leo files:

- When reading a .leo file, Leo will create t.unknownAttributes or v.unknownAttributes ivars for any tnode and vnode object whose corresponding <v> or <t> element in the .leo file contains a foreign attribute.

- The unknownAttributes ivars are a dictionary whose keys are the attribute names and whose values are strings.

- When writing a file, Leo will write "foreign" attributes if a vnode or tnode contains an unknownAttributes ivar.

- The only "native" attributes that Leo expects to find are the "tx" attribute in <t> elements and the "a", "t", "vtag" and "tnodeList" attributes of <v> elements. Everything else is a foreign attribute.

- Leo performs the usual xml escapes on these strings when reading or writing the unknownAttributes ivars.

Plugins may cause Leo to write such foreign attributes simply by attaching unknownAttributes ivars to a vnode or tnode. For example:

v.unknownAttributes = {"vnodeIcon" : pathToIcon}

It would be best not to delete other foreign attributes, so the following would be preferred:

if hasattr(v,"unknownAttributes"): v.unknownAttributes["vnodeIcon"] = pathToIcon else: v.unknownAttributes = {"vnodeIcon" : pathToIcon}

Another way:

try: v.unknownAttributes["vnodeIcon"] = pathToIcon except AttributeError: v.unknownAttributes = {"vnodeIcon" : pathToIcon}
Leo now warns if it can't find either the Icons directory or the LeoApp16.ico icon in that directory.
Leo 4.0 beta 2 October 3, 2003

This is the second public release of Leo 4.0. There are no known serious bugs in this version of Leo. Version 4.0 is the culmination of over a year of collaborative design work and several months of actual implementation.

Highlights of 4.0 beta 2 ------------------------ * Fixed several bugs: - Fixed several problems with reading and writing 4.0 derived files. - Fixed several Unicode-related bugs. - Fixed a crasher in the Go To Line Number command. - Fixed a problem with Importing Python files. * Important improvements: - Added allow_clone_drags setting. N.B. Allows Leo to be used with Aqua. - When running Python 2.3 Leo can now create window icons without third-party packages. - The usual minor improvements and bug fixes.

See the children of this node for full details.

Highlights of 4.0 beta 1 ------------------------ * Improved and simplified format of derived files. * Greatly improved error handling. * Full compatibility will previous versions of Leo.
- Fix undo/redo bug for all commands that change body text in multiple nodes without changing outline structure. - This is a long-standing bug that came to light when trying to make Read @file Nodes undoable.

- Make Read @file Nodes command undoable.

- Fix small bugs in the Go To Line Number command.
The new code prints a warning if Leo's key internal strings are not Unicode.
There were bugs involving saving the "Pattern Match" and "Subroutine Only" find settings.
@nocolor

Class level-code after the method definitions was dropped. For example:

@color

class someclass: def __init__(self): pass

# this code is dropped class_var = None

def some_proc(): pass
The new code ensures that Leo creates and uses only Unicode strings internally.
Fixed Go To Outline command so it doesn't crash on 4.0 files. More work is needed.
Under certain conditions the no sentinels at all were written when writing @rawfile trees.
This is much more natural and less cluttered.
Leo will create the Leo icon in Leo windows automatically when using Python 2.3. The Python Imaging Library and the tkIcon package are only used when using previous versions of Python.
This is the foundation for "foreign" guis.
allow_clone_drags

This is a workaround so that Leo may be used on Aqua. When this setting is zero Leo always moves nodes when dragging. The default is to enable clone-drags.

enable_drag_messages

Allows the user to suppress the message telling whether dragging nodes will move them or clone them. The default is to enable those messages.
After the 4.0 beta 1 release I changed the default so that _new_ format derived files are written if leoConfig.txt does not exist.
leo.py 4.0 beta 1 September 26, 2003

This is the first public release of Leo 4.0. Version 4.0 is the culmination of over a year of collaborative design work and several months of actual implementation.

Please use caution when using this beta version of Leo. It appears very solid and it has been tested by only a few people.

Highlights of 4.0:

** Improved and simplified format of derived files. - Eliminated child indices, extraneous blank lines and @body sentinels. - Eliminated @node sentinels that indicate outline structure. - New @nl and @nonl sentinels indicate where newlines are and aren't. - These changes will largely eliminate unwanted cvs conflicts.

** Greatly improved error handling. - Reading derived files _never_ alter outline structure or links. - Read errors leave the outline completely unchanged. - Broken clone links are gone forever. - As a result, using 4.0 is much safer than all previous versions.

** Full compatibility will previous versions of Leo. - Leo reads all derived files properly, regardless of version. - Leo writes new-format derived files by default, and this default may be changed. - New commands in the read/write menu allow you to explicitly specify the format of derived files.

* New commands: - Write 3.x Derived File. - Write 4.x Derived File. - Import Derived File. - Clear Recent Files.

- Several new plugins. - Several minor bug fixes.

See the children of this node for full details.
The Synchronization Principle simplifies everything and at long last puts Leo on a firm theoretical foundation. This principle states that the "smallest unit of meaning" of any Leo outline is the entire outline itself. In particular, individual derived files have meaning only in the context of a particular outline.

The Synchronization Principle has the following happy consequences:

1. We may assume that all derived files are in synch with the outline that wrote them. This assumption allows Leo to use "hidden machinery" in the outline to associate nodes in the derived file with nodes in the outline. This "hidden" machinery consists of a list of tnodes associated with @file vnodes in the outline. When writing a derived file Leo creates a list of tnodes in the order they were written to the derived file. When reading the derived file, Leo uses this list to associate nodes in the derived file with nodes in the outline.

2. Derived files must get their structure from the .leo file that wrote them. As we have seen, this is done using the hidden machinery in the .leo file. Consequently, there is no further need to represent outline structure in derived files. There is no need any longer either for child indices or for sentinels to represent outline structure. As a result, we can eliminate @+body and @-body nodes entirely because all @+node sentinels are followed by body text.

3. All .leo files must contain full outline structure. Even "thin" outline will contain the full representation of the outline as vnodes.

4. Reading _never_ alters outline structure in the outline. No vnodes are ever created or destroyed and reading never alters clone links. This is true whether or not read errors occur. The read code sets a temporary attribute of tnodes while reading. These attributes are copied to the permanent t.bodyString attribute only if no read errors were encountered.

4. Error recovery is now trivial: Read errors indicate that the .leo files and one or more derived files are out-of-synch. No error recovery is attempted: the @file tree in which the error occured reverts back to its original state.

5. The new Import Derived File command is the _only_ way to recover data from mismatched derived file.
The write_old_format_derived_files option in leoConfig.leo and leoConfig.txt determines which format of derived file Leo's Write and Save commands write by default. If this option is 0 (recommended) Leo writes 4.x format derived files. Otherwise Leo writes 3.x derived files.

Note that you can use the Write 3.x/4.x Derived Files commands in the File:Read/Write menu to write derived files in an explicitly specified format.
This command deletes all entries in the Recent Files submenu except the most recent file. The files themselves are not affected, just the menu entries.
The Import Derived File command imports all the nodes in a derived file into the outline. Unlike the read commands, no outline structure is preserved. This command can import either 3.x or 4.x derived files.

This command is necessary in version 4.0 because there is no other way to read derived files that are out-of-synch with a .leo file.
The Write 4.x Derived Files command and Write 3.x Derived Files command work just like the Write @file Nodes command, except that they write the indicated format off derived file. In contrast, the Write @file Nodes command writes 3.x format derived files if and only if the write_old_format_derived_files = 1 in leoConfig.txt.

All three of these commands automatically save the outline (the .leo file) if any derived file was actually written. This is necessary so that information in the .leo file always remains in synch with all derived files.
Paul Paterson contributed the following new plugins:

mod_autosave.py

Autosaves the Leo document every so often.

mod_timestamp.py

Timestamps all save operations to show when they occur.

word_export.py

Exports an outline to a word document. Clicking "plugins ... word export ... export" exports the selected outline to Word. Word should be running with an open (empty) document before executing this command.
Leo's read code now warns if any non-empty node is unvisited. This check, and the check that headlines match pretty much guarantees that out-of-synch outlines will generate errors. Thus, there is no need a gnx timestamp in @+leo sentinels!
Version 4.0 is a major advance in Leo's error handling. Using 4.0 is much safer than all previous versions. This new error handling applies to all derived files, both 3.x and 4.x format.

Leo's read code never alters the structure of an outline, regardless of whether read errors are encountered. This means that clone links, marks, and all other information in the outline except body text remains completely unchanged during reads. Broken clone links (the dreaded read errors) are gone forever. Also, the new read code makes no changes to any body text until it is known that no read errors have occured. This ensures that absolutely no changes at all are made to the outline if there are read errors.

Leo's read code now writes a message to the log pane whenever it sees that the body text in the derived file does not match the body text in the outline. These messages do not indicate errors, only that the body text has been changed outside of Leo, say in an external editor or by cvs.
Added encoding comments to leoGlobals.py and french plugins

This removes a warning new in Python 2.3.
There is no need to recycle Python objects when the last outline window is being closed.
This is too annoying during testing. The Read @file Nodes command will in fact be made undoable for the 4.0 final release.
This has been changed in Python 2.3 and it probably wasn't ever a good idea...
The read code has much simplified using look-behind rather than look-ahead. Both the read and write code write the file line-by-line without recursion.
leo.py 3.12 Final July 17, 2003

Window sizes are now in pixels rather than grid units. You will need to resize existing outlines (.leo files). Don't panic!

Highlights of this version:

* Plugged a huge memory leak. * Fixed a major bug involving undo. * Added Icon & Status areas to Leo window for use by plugins. * Important new plugins: - The file leoPlugins.leo now contains all plugins. - add_directives.py allows plugins to define new directives. - color_markup.py supports wiki-styled markup. - mod_spelling.py checks spelling (requires aspell to be installed). - nav_buttons.py creates browser-style navigation plugins in the icon area. - plugins_menu.py creates a Plugins menu items for all loaded plugins. - scripts_menu.py creates a Scripts menu for all scripts in scripts folder. - spell - trace_gc traces all changes to memory at idle time. * Other enhancements: - Redesigned the Find panel. Non-functional options are dimmed. - Added @wrap and @nowrap directives. - Added Write Dirty @file Nodes command. - Added several new "stub hooks". - Hyperlinks are now enabled by default. - Leo now loads plugins in alphabetical order. - Leo now remembers the size and location of Leo windows. - Undo now restores the insert point or the selection range after most commands. - Added apply button to Font panel. - Improved the reporting of characters that do not exist in the present encoding. * The usual assortment of bug fixes and minor improvements. - There are no known bugs in this version of Leo.

See the children of this node for full details.
Undoing the following commands could corrupt outlines:

Convert All Blanks, Convert All Tabs, Extract, Extract Names, Extract Section.
v.nodeAfterTree was being called way too often when handling read errors.
- Leo wasn't properly recycling Tk widgets when redrawing the outline pane. This was a _huge_ leak.

- Leo now recycles all objects used by a window when that window closes.

The app.destroyAllWindowObjects() routine calls o.__dict__.clear for all objects o in a window.

- Leo now longer needs destroy() methods in each major class.
c:\prog\leoCVS\leo\src\leoNodes.py:1174: Parameter (encoding) not used

This may have been the cause of some "unicode" problems.
c:\prog\leoCVS\leo\src\leoFrame.py:976: No global (a) found

This may have been the cause of some problems with the log panel.
Leo didn't properly import .cxx files or files with upper case extensions.
The messages "reading: @file x" were not always showing up during Open commands.
Symptom: Sometimes the "reading" messages would not appear when reading an outline.

Cause: Several event handlers are called at unpredictable times during the load process. These may try to reset the log.

Solution: - create app.setLog, app.lockLog and app.unlocklog methods. - call app.lockLog before opening an outline.
The body_pane_wraps setting is now handled properly.
The body keystroke handler wasn't handling the delete character properly.
Methods with the same name confused this command.

The Go To Line Number command will be rewritten for 4.0.
The following was improperly indented in @nosentinel files. The expansion of << class shape declarations >> was indented twice.

class shape(object): << class shape declarations >> # after decls
- Added code to check for errors in the table passed to createOpenWithMenuFromTable.

- Revised code that checks to see whether a temp file has already been created. The old code didn't properly take into account of file extensions when checking for already opened temp files.
Leo now converts all tabs to blanks as you type, not just leading blanks.
- Moved the code into the app class.
This was due to a recent change in getTextSelection.
- Several parts of the code looped on lists that were altered in the loop. - Fixed several bugs in the code that handled shortcuts to Open With menu items.
- Leo's various sort commands did not work properly if two headlines were identical.

- Eliminated the unnecessary and buggy sortSequence routine.
Recent changes intefered with dragging nodes.
Here are the results from running checkClones2Links script on this file:

multipleTargetsInDerivedFiles: 8

(fixed) class nodeIndices (improperly duplicated in leoAtFile.py) (fixed) frame.OpenWithFileName (improperly duplicated in Recent Files submenu) (fixed) recentButtonCallback (improperly duplicated in Mark/Goto submenu)

-- Used several times in same derived file. Will this be valid in LeoN?

<< Append any unused text to the parent's body text >> (used several times in leoImport.py) << Check both parts for @ comment conventions >> (used several times in leoTangle.py) << Compare single characters >> (used several times in leoTangle.py) << Set the default directory >> (used several times in leoAtFile.py)

-- Used (properly) in two different derived files:

replacePatterns (used on purpose in setup.py and postSetup.py)

clonedAtFileNodes: 9

@file ../plugins/add_directives.py @file ../plugins/french_fm.py @file ../plugins/open_with.py @file ../plugins/vim.py @file ../plugins/xemacs.py @file c:\prog\test\multiple.txt @file-nosent nosentCloneTest.txt @silentfile ../doc/PKG-INFO.TXT @silentfile ../doc/README.TXT
Fractions cause problems in some locales. The height/width params weren't used anyway!
- createTopologyList creates a list containing a description of a tree. Used by several of the new scripts.

- importFromPath encapsulates a lot of details of custom importing, and makes it unnecessary to alter sys.path.

- funcToMethod allows users to add methods to any class. Such methods immediately become available to all objects, including existing objects!

- Added do-nothing flush method to redirect class. This routine was added to support LeoN.
- The checkForPossiblyBrokenLinks script does just what it says.

- The checkClones2Links script warns if nodes are used by several derived files.
The new code uses a common base class for all dialogs.
- The new code uses proper Python lists to represent join lists. This greatly clarifies the relevant sections of code, and may even provide a performance boost.

- tnodes now contain both headline and body text. This change creates an important invariant: vnodes with the same vnx's always point to tnodes with the same tnx's. This invariant is vital to resolving conflicts properly.

- rewrote joinList code and move joinLists to tnodes.
- There is now only a single entry point into Leo: the run() routine.

This routine uses an optional fileName arg.

- Created runMainLoop function in leo.py so the call to mainloop() is in a separate function.

This routine was added to support LeoN.
The body_pane_wraps setting is used if neither of these directives is in effect.
This make the Font panel a bit less intimidating to use.
- Added an "icon area" at the top of each Leo frame. Plugins may add buttons to the Icon area at the top of each Leo frame. See the nav_buttons.py plugin for an example of how to use this area.

- Added a "status area" at the bottom of each Leo frame. This area reports the line and column number of the cursor. Plugins may send status messages to this this area.

- Added convenience routines for the icon and status areas to the LeoFrame class. Plugins may use these convenience routines to create, update, manage and destroy fields in the icon and status areas.
These aren't quite ready for prime time.
Hypertext links now enabled by default. Control-click any section reference to go to its definition. I changed the colorer so that it does _not_ underline active hyperlinks in section reference. If a section reference is underlined, it means the definition does _not_ exist. Otherwise the section reference _does_ exist and control-clicking the reference takes you to the defining node in one of its descendents.
The new code is substantially more robust than the old.
Leo was not honoring the requested size of Leo windows.

As the result of this change Leo will open old .leo files much smaller than expected. Do not panic! Simply resize the Leo window and save it. The next time you open the window all will be well.
I have found this command to be highly annoying and unwanted.
This is very important for the spell-checking plugin, and is quite useful in all other situations.
- Added the following stub hooks:

"after-create-leo-frame" "close-frame" "create-optional-menus" "destroy-all-global-windows" "init-color-markup" "new" "redraw-entire-outline" "scan-directives"

See the section called "About hooks" for full details.

- Changed handleLeoHook to doHook. This simplifies the code a bit.
@nowrap

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 "bodyclick2" after normal click in body c,v,event "bodydclick1" yes before double click in body c,v,event "bodydclick2" after double click in body c,v,event "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 "bodyrclick2" after right click in body c,v,event "boxclick1" yes before click in +- box c,v,event "boxclick2" after click in +- box c,v,event "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 "drag2" after start of drag c,v,event "dragging1" yes before continuing to drag c,v,event "dragging2" after continuing to drag c,v,event "end1" start of app.quit() "enddrag1" yes before end of drag c,v,event "enddrag2" after end of drag c,v,event "headclick1" yes before normal click in headline c,v,event "headclick2" after normal click in headline c,v,event "headrclick1" yes before right click in headline c,v,event "headrclick2" after right click in headline c,v,event "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 "hypercclick2" after control click in hyperlink c,v,event "hyperenter1" yes before entering hyperlink c,v,event "hyperenter2" after entering hyperlink c,v,event "hyperleave1" yes before leaving hyperlink c,v,event "hyperleave2" after leaving hyperlink c,v,event "iconclick1" yes before single click in icon box c,v,event "iconclick2" after single click in icon box c,v,event "iconrclick1" yes before right click in icon box c,v,event "iconrclick2" after right click in icon box c,v,event "icondclick1" yes before double click in icon box c,v,event "icondclick2" after double click in icon box c,v,event "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 "select3" after selecting a vnode c,new_v,old_v "set-mark" no when a mark is set c,v (new) "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 "unselect2" after unselecting a vnode c,old_v,old_v "@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.

New in version 3.12:

The following hooks are a new breed of hook, called "stub hooks". Conceptually, these hooks are like calls to stub routines that can be filled in by code in plugins.

tag argument keys in keywords (hook name) overrides when called dictionary argument --------- --------- ----------- ------------------- "after-redraw-outline" no end of tree.redraw c (note 6) "clear-mark" no when mark is set c,v "close-frame" no in app.closeLeoWindow c "color-optional-markup" yes * (note 7) colorer,v (note 7) "create-optional-menus" no (note 8) (note 8) "destroy-all-global-windows" no (note 12) None "create-popup-menu-items" no in tree.OnPopup c,v,event (new) "enable-popup-menu-items" no in tree.OnPopup c,v,event (new) "init-color-markup" no (note 7) colorer,v,s,i,j,colortag (note 7) "new" no start of New command old_c,new_c (note 9) "redraw-entire-outline" yes start of tree.redraw c (note 6) "scan-directives" no in scanDirectives c,v,s,old_dict,dict,pluginsList (note 10) "set-mark" no when mark is set c,v "show-popup-menu" no in tree.OnPopup c,v,event

Notes:

(6) These hooks are useful for testing.

(7) These stub hooks allow plugins to parse and handle markup withing doc parts, comments and Python """ strings. Note that these hooks are _not_ called in Python ''' strings. See the color_markup plugin for a complete example of how to use these hooks.

(8) The "create-optional-menus" stub hook is called when Leo is creating menus, at the place at which creating new menus is appropriate. Therefore, this hook need only create new menus in the correct order, without worrying about the placement of the menus in the menu bar. See the plugins_menu and scripts_menu plugins for examples of how to use this hook. Leo now executes hooks in alphabetical order, so that the Plugins menu will be created before the Scripts menu.

(9) The "new" stub hook is called at the beginning of the code that implements the New command. This provides a much easier way of recognizing when Leo is about to create a new Leo window.

(10) The "scan-directives" stub hooks is called inside the scanDirectives global function. The dictionary returned by scanDirectives now contains an item whose key is "pluginsList". The value of this item is a list of tuples (d,v,s,k) where:

- d is the spelling of the @directive, without the leading @. - v is the vnode containing the directive, _not_ the original vnode. - s[k:] is a string containing whatever follows the @directive. k has already been moved past any whitespace that follows the @directive.

See the add_directives plugins directive for a complete example of how to use the "scan-directives" stub hook.

(11) The "close-frame" stub hook is called in app.closeLeoWindow just before removing the window from app.windowList. The hook code may remove the window from app.windowList to prevent app.closeLeoWindow from destroying the window.

(12) Leo calls the "destroy-all-global-windows" hook in app.destroyAllGlobalWindows(). This hook gives plugins the chance to clean up after themselves when Leo shuts down.
Leo now supports two new stub hooks: "clear-mark" and "set-mark". Both fully update the marks window, so it is important that they not be called "too often", and they aren't.

As a side effect, it is no longer necessary to update the marks window after every command that affects marks, a nice simplification and generalization of the code.

While I was at it, I moved the two dialog classes used only by the nav_buttons plugin out of leoDialog.py and into nav_buttons.py where they belong.
Added the "create-popup-menu", "enable-popup-menu-items" and "show-popup-menu" hooks.
Added the "create-popup-menu", "enable-popup-menu-items" and "show-popup-menu" hooks.
This plugin allows users to create new directives.

As an example, this plugin creates a new @markup wiki directive.
This plugin supports coloring for markup in doc parts and Python triple-double-quoted strings.

At present, this plugin supports wiki-style markup, and it would be easy to change the style of markup.
This plugin adds 4 new buttons to the icon area at the top of each Leo window:

- Back and Forward arrows that work like a typical web browser. Clicking the back arrow button takes you to the last node in the tree you visited. Clicking the forward arrow button moves you forward. The forward arrow button is dimmed unless you have clicked the back arrow button recently.

- The Recent button opens a non-modal dialog showing all the nodes you have visited recently. This dialog is updated cleanly by the tree.select method. You will see this entries change dynamically. There are buttons in this dialog for clearing individual entries or clearing all entries. Double-clicking any entry in this dialog takes you to that node, as does clicking the Go button.

The entries in the Recent Nodes pane do _not_ correspond to the order implied by the Forward and Back arrows. Rather, the most recently visited node is on top, followed by the next most recently visited node, and so on, with the additional feature that nodes appear only once in the list.

- The Marks button opens a non-modal dialog showing all marked nodes in the tree. Marking or unmarking nodes in the tree adds or deletes entries from this dialog. Double-clicking any entry in this dialog takes you to that node, as does clicking the Go button.
This creates a plugins menu describing each loaded plugin.
The scripts_menu.py plugin creates a Scripts menu for all scripts in scripts folder.
- Leo now scans all .py files in the plugins folder when looking for plugins.

Filenames need not start with mod_, and Leo scans only the plugins folder.

- Leo loads plugins in alphabetical order.

- Leo now prints only a brief summary of the number of files loaded & examined to the log file.
1. Created config, doc, examples, plugins, scripts, src, test & tools folders.

2. Removed the following files:

- nsi.leo file. This was a duplicate of code in LeoPy.leo. - bdist.bat, go.bat, help.bat, sdist.bat. - leo.rc and leo.res. These were resource files used by the Delphi classes.
These were written by Frank Merenda.
leo.py 3.11.1 May 1, 2003

This version fixes two bugs:

- Version 3.11 had startup problems on some Linux systems.

- Creating clones by control dragging did not always work. This bug has existed in Leo for a long time.
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.

leo.py 3.11.1 May 1, 2003

This version fixes the following bugs:

- Leo would crash on startup on some Linux systems due to a problem with Leo's local copy of getpreferredencoding.

- Leo sometimes did not set clone bits properly after creating a clone by control-dragging a node. This bug has existed for about 6 months!

leo.py 3.11 April 27, 2003

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.
New in Leo 3.10: Leo allows you to choose whether body text in @root trees will start in code mode or doc mode by default.

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

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.

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 in the config subdirectory. 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

What is a plugin?

A plugin is a .py file that appears in Leo's plugin subdirectory. Leo tries to import all such files while starting up. This is done at "start1" time, that is, in the call to doHook("start1") in the run function in leo.py. Plugins exist to define "hook code".

New in 3.11: Leo looks in the plugins folder for .py files. Leo tries to import all such files at startup ("start2") time.

New in 4.1: Leo enables only plugins whose name appears in pluginsManager.txt. Leo loads plugins in the order in which they appear in pluginsManager.txt. Leo loads no plugins if pluginsManager.txt does not exist in the plugins directory.

What is hook code?

Leo automatically calls hook code at various times during Leo's execution. Typical hooks are:

- "start2", called at the end of Leo's startup process, - "command1", called before executing any of Leo's commands - "idle", called at Tk's idle time, when nothing else is happening,

See the documentation for hooks in this file for full details.

N.B. Plugins can use "start2" hooks to override any of Leo's classes. See "Overriding functions, methods & classes" in leoPlugins.leo for several examples of how to do this.

How do plugins work?

Plugins register themselves when Leo imports them using the following code, which should appear as the outer level of the .py file:

@color

"""A description of this plugin"""

registerHandler("xxx", onXXX) __version__ = "1.2" plugin_signon(__name__)

@nocolor

Line by line:

1) """A description of this plugin"""

This is a Python doc string describing the plugin. This string becomes the value of the __name__ attribute for the module (.py file).

2) registerHandler("xxx", onXXX)

This line registers a handler called onXXX for the hook called "xxx". See the documentation for hooks for a complete list of hook names that you can use instead of "xxx". onXXX is the name of a function or method, presumably defined in the plugin file, that Leo will call at "xxx" time. For example:

registerHandler("idle",onIdle)

will cause Leo to call the onIdle function at "idle" time.

You can pass a list of hook names as the first argument to registerHandler. For example:

registerHandler(("bodykey1","bodykey2","headkey1","headkey2"), onKey)

3) __version__ = "1.2"

This assigns a version attribute to module. At present this attribute is used by the plugin code that creates Leo's Plugins menu.

4) plugin_signon(__name__)

This line increases the count of loaded modules. The present code for plugin_signon does not actually print separate signons--it seems to verbose.

About hook handlers

See the "About hooks" section of leoPlugins.leo for full documentation about how to write hook handler routines, like onXXX or onKey in the examples above. Basically, each hook should have this pattern:

@color

def onXXX (tag,keywords): c = keywords.get("c") otherKeyword = keywords.get("otherKeyword") << do something with c and otherKeyword, etc. >>

@nocolor

In other words, keywords is a Python dictionary containing the keyword arguments passed to the hook handler. See the See the "About hooks" section of leoPlugins.leo for a list of the keywords passed to each hook handler.

The tag is the name of the hook. For example, tag would one of "bodykey1","bodykey2","headkey1","headkey2", for the onKey hook handler in the example above.

What's next?

It's one thing to know how to create a plugin. It's another to know how to actually change Leo. Obviously, some study is needed. The place to start your study is LeoPy.leo. In particular, study very carefully the section called "Overview of code". Leo is a highly modular program, and Python is ideally suited to this style of programming.
@nocolor

New in 3.11: Leo looks in the plugins folder for .py files. Leo tries to import all such files at startup ("start2") time.

New in 4.1: Leo enables only plugins whose name appears in pluginsManager.txt. Leo loads no plugins if pluginsManager.txt does not exist in the plugins directory.

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

# 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 error checking:

At present Leo will disable all hooks if any hook throws an exception. In practice this isn't a big deal, but something better could be done.

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)
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. - ext is a file extension or None.

When the user selects the Open With item corresponding to the table item Leo executes command(arg). 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)
New in 4.0: Leo can now read and write "foreign" attributes in <v> and <t> elements in .leo files:

- When reading a .leo file, Leo will create t.unknownAttributes or v.unknownAttributes ivars for any tnode and vnode object whose corresponding <v> or <t> element in the .leo file contains a foreign attribute.

- The unknownAttributes ivars are a dictionary whose keys are the attribute names and whose values are strings.

- When writing a file, Leo will write "foreign" attributes if a vnode or tnode contains an unknownAttributes ivar.

- The only "native" attributes that Leo expects to find are the "tx" attribute in <t> elements and the "a", "t", "vtag" and "tnodeList" attributes of <v> elements. Everything else is a foreign attribute.

- Leo performs the usual xml escapes on these strings when reading or writing the unknownAttributes ivars.

Plugins may cause Leo to write such foreign attributes simply by attaching unknownAttributes ivars to a vnode or tnode. For example:

v.unknownAttributes = {"vnodeIcon" : pathToIcon}

It would be best not to delete other foreign attributes, so the following would be preferred:

if hasattr(v,"unknownAttributes"): v.unknownAttributes["vnodeIcon"] = pathToIcon else: v.unknownAttributes = {"vnodeIcon" : pathToIcon}

Another way:

try: v.unknownAttributes["vnodeIcon"] = pathToIcon except AttributeError: v.unknownAttributes = {"vnodeIcon" : pathToIcon}

See the section called "Recommended conventions for plugins" for advice about how to avoid potential conflicts in naming new attributes.
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

New in Leo 3.10:

@file-asis is a synonym for @silentfile. @file-nosent is a synonym for @nosentinelsfile. @file-noref is a synonym for @rawfile.
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.
@nocolor
Today I have had series of important insights regarding Leo's implementation. To state my conclusion first:

** No representation of clones is likely to be better than the present way using vnodes and tnodes. **

Since Leo's earliest days I have wondered whether it might not be possible to improve how Leo represents clones. The present scheme laboriously creates and deletes "dependent trees" of vnodes when a node is moved that is a clone or a descendent of a clone. Couldn't we do better if we represented clones as a single tree of nodes that is shared by other nodes?

Today I thought of a new way of looking at things. You can think of it as a thought experiment, though at first I thought of it as a viable implementation strategy. This new point of view has greatly clarified the essentials of the situation. It strongly suggests the conclusion stated above.

The thought was this: suppose vnodes are not "real" or "permanent", but merely epiphenomena of the underlying "reality", the shared nodes. I thought of vnodes as existing in "the floating world." The "real" nodes, the potentially shared nodes, I call inodes (information nodes.) All fundamental information would reside in inodes.

Furthermore, suppose that vnodes are created by the tree class _as a by-product of redrawing the screen_. This actually is a pretty clever idea. It has the following big advantages:

1. Only a single iterator is ever needed to traverse the tree of inodes. This iterator is called only by the tree class when the tree needs to be redrawn. The result of the tree traversal is the tree of vnodes. Most other Leo code (and user scripts) can use and traverse the tree of transient vnodes without any modifications.

2. Creating and destroying dependent trees happens automatically as the result of the tree traversal. There is no need for the complex special cases found in the present vnode class.

So this seems like hot stuff. Very clever indeed, if not a big breakthrough. However, a closer examination reveals that almost nothing is gained by this "cleverness". If this clever way doesn't really improve matters, it is most unlikely that any other scheme will. This insight is the real breakthrough.

So let us look at the details and implications of generating temporary vnodes while redrawing the screen:

1. Because inodes may be shared, they need not have unique parents. But the generated vnodes _will_ have unique parents. Therefore, all the code that traverses vnodes will work just fine.

2. Alas, there is a huge costs in creating vnodes on the fly. We must create: a) the vnodes themselves, b) the Tk.Text widgets corresponding to visible vnodes and c) all the data in the vnodes, including links to other vnodes and links to inodes.

These costs can't be optimized away.

3. Most importantly, even if vnodes are "ephemeral" they are in fact essential. We need _unique_ vnodes to represent _different_ areas on the screen, even if those vnodes are on the same join list. For example, we need _distinct_ Tk.Text widgets in order to draw the screen at all.

4. And join lists too are essential! Indeed, if the headline of a cloned node changes, _different_ Tk.Text widgets must be updated on the screen. No matter how elegant the implementation of inodes, we _still_ must have a way of quickly updating all joined vnodes _on the screen_.

Summary

At long last the situation has become clear:

Fact 1: Separate vnodes are required to redraw the screen properly. Fact 2: Join lists (whether in vnodes or tnodes) are also essential so that items _on the screen_ can be redrawn quickly. Fact 3: Leo must create and destroy dependent trees of vnodes, _because those trees appear and disappear from the screen._ Fact 4: Using shared inodes will not change either fact 1, 2 or 3.

In short, nothing would be gained by trying to represent clones as shared trees because:

** join lists and dependent trees are needed to draw the screen correctly **

This came as a great surprise to me. It resolves a question that has been on my mind for at least 8 years. As a result, there is no need to do any prototyping of inodes or iterators. Inodes will never see the light of day.

Edward

P.S. As I have said before, the present representation of join links can most likely be improved greatly. I plan to do this (and probably not much else) for 4.0.

P.P.S. There are other implementation problems with temporary vnodes. For example, what would carry marks? These problems aren't really part of the discussion, but they would be quite important if one actually rewrote the code!

P.P.P.S. I'll look at Gil's ideas (and any others) thoroughly before fixing 4.0 in stone. As Gil suggests, designing 4.0 properly is more important than releasing 4.0 next week :-)

EKR
Actually, my first analysis was incomplete. Suppose we eliminate vnodes completely? Leo would then redraw the screen directly from the inodes. This wouldn't be so hard: each inode would contain an list of Tk.Text widgets. The drawing code merely has to place the an unused widget in the correct place in the screen (the Tk.Canvas).

In some sense, the array of Tk.Text widgets in each inode is like the join list, but only visible nodes need be on this list. Furthermore, this list only needs to be updated when the outline is actually redrawn. It would be easy to insert or delete new Text widgets in this list. There are lots of possibilities, all easy to do in Python.

However, replacing vnodes with inodes is likely to be a very bad idea, for several reasons:

1. As I mentioned earlier, this implementation would require massive changes throughout Leo's code. All "user" code would have to use an iterator to traverse the outline. In particular, the fundamental code to manage the outlines would be changed significantly and would almost certainly become more complex.

2. Inodes complicate Leo from the user's point of view. The present data model is much better because

** The vnode tree corresponds directly to what the user sees on the screen **

Giving up this correspondence seems like a big step backward. We could try to recreate the node list during redrawing, but that seems likely to create a real mess. Impermanent vnodes might well be worse than no vnodes at all.

3. As mentioned in an earlier post, marks present a problem without vnodes. Perhaps each inode could contain a list of locations (in the full tree traversal) that should be marked. However, updating this kind of list when the outline changes could be very complex. It wouldn't be horrible to say that all joined nodes must be marked in synchronization, but it wouldn't be a step forward.

4. There are other ways of improving Leo's performance without touching the data model at all. Rewriting the vnode, tnode, atFile and fileCommands modules as C++ code in a Python extension will almost certainly double the speed of key operations. As mentioned in the first post, there are optimizations that the vnode class can do to avoid deleting dependent trees and then immediately recreating them. And don't forget that the average speed of our computers doubles every 2-3 years or so. So just waiting for a faster machine is a highly effective optimization!

Revised conclusions

Replacing vnodes with inodes is possible, and it might even provide some performance gains for huge outlines containing many clones. However, replacing vnodes with inodes would be an extremely high risk project: very complex, with likely negative consequences. I doubt that there will ever be a need even to consider such a project.

The present code base is plenty good enough for most outlines, and there are much simpler and better ways to speed up key outline operations. In short: I have no more interest in inodes and their attendant complexities.
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.

New in version 4.1: The first time you start Leo, Leo will ask you for a unique identifier. If you are using cvs, use your cvs login name. Otherwise your initials will do.

Leo stores this identifier in leoID.txt, so you can change this identifier at any time by chaning leoID.txt.
@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: actionscript, c, c++, cweb, elisp, html, java, latex, objective-c, pascal, perl, perlpod, plain, python, rebol, shell or tcltk. 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.
New in version 3.12: By default, the body_pane_wraps setting in leoConfig.txt controls whether body text wraps. You may override this setting for a particular tree using the @wrap and @nowrap directives.
@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 To commands save the topmost Leo window to a file. The Save As command changes the name of the outline being edited; the Save To command does not.

The Revert command reloads a file, discarding any changes made to the file since it was last saved.

New in Leo 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.

New in Leo 4.0: The Clear Recent Files command deletes all entries in the Recent Files submenu except the most recent file. The files themselves are not affected, just the menu entries.

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.

New in Leo 3.12: The Write Dirty @file Nodes command writes all @file trees that have been changed.

New in Leo 4.0: The Write 4.x Derived Files command and Write 3.x Derived Files command work just like the Write @file Nodes command, except that they write the indicated format off derived file. In contrast, the Write @file Nodes command writes 3.x format derived files if and only if the write_old_format_derived_files = 1 in leoConfig.txt. Auto Save: all three of these commands automatically save the outline (the .leo file) if any derived file was actually written. This is necessary so that information in the .leo file always remains in synch with all derived files.
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.

New in Leo 4.0: The Import Derived File command imports all the nodes in a derived file into the outline. Unlike the read commands, no outline structure is preserved. This command can import either 3.x or 4.x derived files. This command is necessary in version 4.0 because there is no other way to read derived files that are out-of-synch with a .leo file.
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 other lines of selected 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.

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.

New in version 4.1: Body text may now contain @directives and section references. This is a major step forward: you can now use all of Leo's features to organize scripts that you execute interactively.

Section definitions must appear in the node containing the script or in descendent nodes. Leo "preprocesses" all scripts by simulating the writing of a derived file to a string.

The Execute Script command sets app.scriptDict["script1"] to the value of the script before preprocessing, and sets app.scriptDict["script2"] to the value of the script after preprocessing. Scripts may examine and change app.scriptDict as they please.
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.

New in Leo 4.1: The Esc key dismisses the dialog. Pressing the enter key is the same a pressing the Find button.

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.

The Sort Siblings command sorts all siblings of the present node in alphabetical order.

New in version 4.1: The Hoist command redraws the outline making the present node the root node. The outline itself is unaffected by the Hoist command. The De-Hoist command removes one level of hoisting.
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 Open Compare Window command opens a dialog that allows you to compare two files.

This is not really a general-purpose file-compare dialog. The purpose of this window is to show what sentinels a derived file contains. One file is assumed to contain sentinels, the other file is assumed not to contain sentinels.
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 body. v.setHeadStringOrHeadline(head) # set headline text of v to head.
@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. The Borland Delphi classes were 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.
@nocolor

The 4.0 file format meets all of the following goals:

- The minimum of sentinels needed to properly recreate the outline. - A robust way of telling whether newlines belong to sentinels or not. - A minimum of intrusion and ugliness.

Changes to derived file format:

The @+leo sentinel indicates whether the format is 3.x or 4.x.

@+node and @-node sentinels no longer contain child or clone indices. Therefore, such sentinels do not change when outline nodes are inserted, deleted or moved. This will grealy reduce (but not entirely eliminate) cvs conflicts involving sentinel lines.

Leo generates @+node sentinels only for nodes containing body text. Leo no longer generates other @+node sentinels to indicate outline structure. As a result, there is no longer any need for @+body sentinels.

The @nl and @nonl sentinels adjust contribution of _previous_ sentinels. This eliminates all lookahead logic and greatly simplifies the read code.

The @+others and @<< sentinels indicte leading whitespace appearing before @others directives and section refs.

Leo writes the @afterref whenever any non-whitespace text appears after a section references.

The @+doc and @+at sentinels now contain the whitespace that follows the corresponding @doc or @ directives.
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.
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://webpages.charter.net/edreamleo/intro.html

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.
Leo's clones provide an excellent way of solving any organizational problem. The fundamental principle is this: Clones create multiple views of data.

For example, 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's @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.
Internally, Leo represents all strings as unicode. Leo translates from a particular encoding to unicode when reading .leo files or derived files. Leo translates from unicode to a particular encoding when writing derived files. You may see strange looking characters if your text editor is expecting a different encoding.

The encoding used in any derived file is shown in the #@+leo line like this: #@+leo-encoding=iso-8859-1. Exception: the encoding is UTF-8 if no -encoding= field exists. You can also use the @encoding directive to set the encoding for individual derived files.

If no @encoding directive is in effect, Leo uses settings in leoConfig.leo/.txt to translate to and from unicode. These settings are in the section in leoConfig.leo called

<< General configuration options, especially read-only mode >>

Here is a summary of those settings:

default_derived_file_encoding

This is the encoding used for derived files if no @encoding directive is in effect. This setting is also used to encode files created by the Tangle commands. The default is UTF-8 (case not important).

new_leo_file_encoding

This is the encoding specified in the following line of new .leo files: <?xml version="1.0" encoding="UTF-8"> The default is UTF-8 (upper case for compatibility for old versions of Leo). Important: once a .leo file is created the <?xml..."> line can only be changed by hand. Changing the <?xml..."> line by hand may cause unicode errors the next time the .leo file is loaded, so you should change the <?xml..."> line by hand only when first creating a .leo file.

tk_encoding

This is the encoding that Leo uses to communicate with Tk text widgets. You would typically use this setting only in an emergency. The section called << set app.tkEncoding >> in app.finishCreate sets this encoding as follows:

a)using the tk_encoding setting if it exists, b) using locale.getpreferredencoding if it exists, or the equivalent code in pre-python 2.3 versions, c) using sys.getdefaultencoding d) defaulting to "utf-8"

Neither b nor c are guaranteed to give a valid encoding in all systems, and Leo will ignore invalid encodings returned from b or c. Therefore, setting tk_encoding in leoConfig may be needed.
The encoding used in the file being imported doesn't match the encoding in effect for Leo. You have two options: Use the @encoding directive in an ancestor of the node selected when doing the Import command to specify the encoding of file to be imported.Set the tk_encoding option in leoConfig.leo/.txt to the encoding you normally use. See the previous answer.
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 @silentfile 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.
The import commands insert @ignore directives in the top-level node. Leo does this so that you won't accidentally overwrite your files after importing them. Change the filename following @file or @root as desired, then remove the @ignore directive. Saving the outline will then create the file.
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

Important: 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.
Here is a letter from Zvi Boshernitzan which might be helpful:

I was having trouble disabling <?php with comments (and couldn't override the comment character for the start of the page).

Finally, I found a solution that worked, using php's heredoc string syntax:

------------------------------- @first <?php @first $comment = <<<EOD EOD;

// php code goes here. echo "boogie";

$comment2 = <<<EOD @last EOD; @last ?> --------------------

-- or --

--------------------- @first <?php @first /* */

echo "hi";

@delims /* */ @last ?> ------------------------
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 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.leo.

See the child of this node for full details.
Everything in an @file tree must be part of the derived file: orphan and @ignore nodes are invalid in @file trees. This restriction should not be troublesome. For example, 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.
Step 1: Set use_plugins = 1 in leoConfig.leo, then execute the Tangle All command.

Or you can set use_plugins = 1 directly in leoConfig.txt.

Step 2: For a particular @file node in leoPlugins.leo, enable the code that calls registerHandler by changing if 0: to if 1:

For example:

if 1: # Register the handlers... registerHandler("start2", onStart)

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 plan 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.
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.
@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
@nocolor

I would like to set up a system for planning, discussing and implementing major additions to Leo. Python has PEP's (Python Enhancement Proposals). I'd like to start something similar called Leap's (Leo Enhancement & Advancement Proposals).
100 Series: Leaps that apply to the Leap process itself.

Leap 101: How to administer Leaps.

- Assigned to nobody. - No formality needed yet.

- Assigned to nobody. - Create, plan, promote & administer a gathering of Leo enthusiasts. - Part of the planning would be to create a date and setting for the conferences.
- Project abandoned. Prototypes largely complete. - Assigned to EKR. - Remove tnodes as a fundamental part of Leo. - Represent clones as link nodes and target nodes. - Create vxnodes as needed. (Some new thoughts about this are coming).

- Project abandoned.

- Assigned to EKR. - Sentinels are immutable, with mutable information following on separate lines. - We could create such a file format without Leap 201. - Leap 201, if implemented, will imply changes to this Leap.

- Assigned to EKR. - More flexible and more powerful than present hand-drawn code. - Ideally, it would be a path to anygui. - No firm commitment to this Leap: it depends on further study.

- Assigned to EKR and Paul Paterson.

This will be released in 3.12.
Produce a plugin (with related design) that allows people to share all or part of .leo files via a central server.

- Assigned to nobody. - This plugin need _not_ address itself to issues raised in Leap 302: it is a prototype for experimentation. The initial aim of this Leap would be to produce a starting point design that would be incorporated into this Leap for review.

- Assigned to nobody - This plugin need _not_ address itself to issues raised in Leap 301: it is a prototype for experimentation. The initial aim of this Leap would be to produce a starting point design that would be incorporated into this Leap for review.
Open Discussion

RE: Status report: ten breakthroughs 2003-05-30 12:50 About merging / CVS etc:

Perhaps it helps to think about the big picture a bit, I mean about what do we really want / is practical.

CVS is a de-facto standard on the internet for collaboration, so being compatible is highly desirable. Unfortunately CVS has a series of shortcomings, particularly for Leo.

CVS merges on a line-by-line basis, so interdependencies of code are ignored (I think all other tools I know of do the same). Now, I believe this is ok for large sets of files, where mutiple changes to the same function or file are rare. Using CVS to merge only single Leo nodes (body text) would be rather silly though: Mostly these texts are short (a page or so) and one single function (or less). Multiple changes in such a codde snipped are likely to be interdependent, and I would rather like to look at the different texts rather than have them merged automatically.

On the other hand, I don't mind if the versioning or merging tool at hand allows me to update any nodes I did not touch myself. So rather than CVS-like merging, I would like to see which nodes are being affected by the change, then have it done automatically for thouse nodes I don't worry about, but by hand (or interactively) for thouse I changed myself too, or for thouse I worry about, because they are related to my own changes.

In other words, I would like to see a merge tool, that understands versions, can tell me which nodes in a Leo file have changed, remembers which changes I made myself, and gives me the choice, tree branch by tree branch, to automatically accept the changes, or screen them interactively.

I think Leo should have such a tool built-in.

- Josef
@ Recent diary entries are in LeoPy.leo.
This has been changed in Python 2.3 and it probably wasn't ever a good idea...
This removes a warning new in Python 2.3.
However, no change was required to cutomizing.html. In other words, the online description of plugins and hooks was already good.
@nocolor

Put install/uninstall in base directory of distribution

Put leo-script.txt goes in ./scripts/install/leo-script.txt
The new values are:

"initial_window_height" : 600, # 7/24/03: In pixels. "initial_window_width" : 800, # 7/24/03: In pixels.
There was no way to remove a default shortcut so that no shortcut was used.

The problem:

You attempted to remove a shortcut merely by removing an entry in the shortcuts section of leoConfig.leo/.txt. That doesn't work: the default shortcut remained and conflicted with a new shortcut. BTW, Leo does need defaults for when leoConfig.txt does not exist.

The workaround:

Specify in leoConfig.leo/.txt some shortcut to override the conflicting shortcuts. The proper fixes (now on cvs):

1. Leo now sends a warning both to the console window (if any) and to the log pane when it detects a duplicate shortcut.

2. Leo now allows leoConfig.txt to remove the shortcut for a command by specifying None (case doesn't matter) as the value of the shortcut.
These aren't great tests, and they do show that now exceptions are thrown in the colorizer.
Also fixed some bugs (missing commas) in the list of rebol keywords.
This was a most irritating bug.

The problem was in tree.OnDeactivate. The calls to tree.endEditLabel() and tree. self.dimEditLabel() were interfering with the double-click logic.

What I did:

- Changed tree.OnDeactivate so that it only tried to deactivate the headline if the focus was previously in the tree. Simple, effective.

- Added get_focus to leoGlobals.py.

- Removed unused frame.active and frame.activeFrame ivars.

- Changed set_focus so it only call widget.focus_set if there has been a real change in focus. - This has no effect, and could cause problems due to bad params being passed.
Added openWithFileName to leoGlobals.py.

- This allows startup code to create a frame without an existing frame. - frame.OpenWithFileName now just calls this global.

Changed startup code so that:

- Only one window is ever created on startup. - Windows are not witdrawn when they are created.
This shows Leo's nav buttons and status line.
Fixed minor bugs in convertAllBlanks and convertAllTabs (reported no change when there was a change.)
This constitutes a major changes to Leo's fundamental vnode code.

v.copyTree now creates a free-standing tree that may be linked into an existing outline using any of the v.linkX routines. This is a major simplification that was long overdue.

N.B. Copied trees share tnodes with the original code. It would be easy to make this optional, and at present all code expects tnodes to be shared.

N.B. Nodes of the copied trees are _not_ joined to the original tree. That is, the copied vnodes v do not appear in v.t.joinList.
The following commands are affected:

Convert All Blanks, Convert All Tabs, Extract, Extract Names, Extract Section.

What I did:

- Rewrote undoer.undoReplace. - It is not enough to call createDependents or destroyDependents. -

- Called setUndoParams with oldText, newText, oldSel, newSel params.
The new code assumes nothing about when event handlers get called.
- added set_focus and force_focus utilities for better tracing. - changed drawText so it calls setNormalLabelState when v == editVnode. idle_redraw no longer interferes with the headline being edited. - setNormalLabelState now selects all text and calls set_focus. - added callers keyword param to trace. - simplified c.insertHeadline: it no longer has to avoid redraws.
This must be done to plug a major security hole.
This was pretty easy. What I did:

- Changed getTextSelection so that it returns a tuple (insert,insert) instead of (None,None) if the selection range is empty. This does most of the work! However, the changeSelection code must now explicitly test to see whether start==end

- Pass an oldSel param to most calls of onBodyChanged.

- Added a newSel param to onBodyChanged and idle_body_key.

- Forced the focus to the body pane after pressing undo/redo buttons in the Spell Checker. This makes the selection visible.
Shift-Control-O was causing a crash. Not surprising since it had no business in call OnOpenWith.

What I did:

- removed event param from OnOpenWith. - Created an Open With Submenu in << create the top-level file entries >> This has the effect of not allowing a shortcut for this menu. - Use app().hasOpenWithMenu to enable or disable Open With menu. - Removed Shift-Control-O setting in leoConfig.leo/.txt.
Made this change to the Color, Compare, Font, Prefs and Spelling panels. However, this doesn't set the focus as I would like.
dict was being altered in a loop where it shouldn't be. Changed the name of the var in the loop from dict to d.
Symptom: Sometimes the "reading" messages would not appear when reading an outline.

Cause: Several event handlers are called at unpredictable times during the load process. These may try to reset the log.

Solution:

- create app.setLog, app.lockLog and app.unlocklog methods. - call app.lockLog before opening an outline.
Changed destroyOpenWithFilesForFrame and createOpenWithTempFile so they don't loop on a list that is being modified.
@color

# Commanders and subcommanders. class Commands (baseCommands): pass class atFile (baseAtFile): pass class fileCommands (baseFileCommands): pass class leoImportCommands (baseLeoImportCommands): pass class tangleCommands (baseTangleCommands): pass

# Helper classes. class colorizer (baseColorizer): pass class config (baseConfig): pass class undoer (baseUndoer): pass

# Key data classes. class LeoFrame (baseLeoFrame): pass class leoTree (baseLeoTree): pass class tnode (baseTnode): pass class vnode (baseVnode): pass

# Dialogs and other panels class leoColorPanel (baseLeoColorPanel): pass class leoColorNamePanel(baseLeoColorNamePanel): pass class leoComparePanel (baseLeoComparePanel): pass class leoCompare (baseLeoCompare): pass class leoFontPanel (baseLeoFontPanel): pass class leoDialog (baseLeoDialog): pass class LeoPrefs (baseLeoPrefs): pass
- Made functions into class methods. - Make sure to close pipes after every command. - Rewrite local dictionary in alph order.
At present setup.py must be run from the top level leo directory. Hopefully this will be fixed.

Also, the linux install script won't work.
c:\prog\leoCVS\leo\src\leoAtFile.py:2791: Format string argument count (1) doesn't match arguments (2)

c:\prog\leoCVS\leo\src\leoFrame.py:976: No global (a) found

This may have been the cause of some problems with the log panel.

c:\prog\leoCVS\leo\src\leoNodes.py:1174: Parameter (encoding) not used

This may have been the cause of some "unicode" problems.
The old code had no chance of working. I think I actually understand what is happening now...
The body_pane_wraps setting is used if neither of these directives is in effect.
Added code to check for errors in the table passed to createOpenWithMenuFromTable.
We have to do the << set removeTrailing >> logic when the delete key is pressed, and that wasn't happening.

The new code now remembers properly when the user deletes a trailing newline.
created frame.setInitialWindowGeometry.
1. Added destroy routines for all major classes

This allows most window objects to be garbage collected when a window closes. In practice it is _very_ difficult to remove all references to all window objects. Breaking those links "by hand" in the destroy routines is a more pragmatic approach.

The big drawback is that destroy methods must be updated when a new ivar is added to a ctor that refers to other objects. In practice, such ivars aren't added often.

With these new routines in place Leo does, in fact, recycle most objects from windows when a window closes. This plugs the last (relatively minor) leak in Leo.

2. Created a _general_ way of clearing all ivars of an object.

o.__dict__.clear() does the job!

3. Remvoed all destroy routines, replacing them by app.destroyAllWindowObjects().

This is a simple, effective, pragmatic approach, and it doesn't require changes when new ivars are added to ivars!
These are not needed now that Python has a gc that can recover cycles.

Everything should go away because:

- app().windowList no longer points to a frame. - frame.top.destroy() eliminates all the Tk pointers.
I did the following:

- Searched for use_gnx, using_gnx, use_pre_4pt0_file_formats. - Removed all 4.0 constants from << atFile constants >> - Removed the setting corresponding to use_pre_4pt0_file_formats config ivar.

As I was doing this I moved routines and code snippets into the Abandoned Code from 4.0 node.

This is a major step forward for Leo! The code base is mostly back to 3.10 days, with some improvements to vnodes and tnodes.
Fractions cause problems in some locales. The height/width params weren't used anyway!
This was all that was needed to correct the problem with new windows.
- Added self.widgets list and associated logic in deleteBindings.

We can't rely on the Tk.Widget to delete the associated Python objects!

- Added printGarbage routine to leoGlobals.py.
This is significant for two reasons:

1. It shows how to reduce the number of Tk widgets allocated to a constant (linear in size of the canvas).

2. It shows that incremental drawing could be used with vxnodes.

This code took a lot of work. Issues involving the scrollbar resulted in a two-pass drawing algorithm. The first pass populates the canvas. The second pass sets the visible area properly after the scrollbar is set properly.

The visual appearance of scrolling appears identical, regardless of whether incremental allocation is used. This is an important result.

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

Pros:

- substantially reduces the number of Tk widgets that get allocated.

Cons:

- More complex code (it appears solid) - A two-pass screen redrawing algorithm (it appears fast enough) - Not all nodes become visible immediately when sliding the scrollbar. This is mostly likely because the actual redrawing of the screen happens at idle time.
The new code is simple, elegant, efficient, flexible. A good day.
Here are the results from running checkClones2Links script on this file:

multipleTargetsInDerivedFiles: 8

(fixed) class nodeIndices (improperly duplicated in leoAtFile.py) (fixed) frame.OpenWithFileName (improperly duplicated in Recent Files submenu) (fixed) recentButtonCallback (improperly duplicated in Mark/Goto submenu)

-- Used several times in same derived file. Will this be valid in LeoN?

<< Append any unused text to the parent's body text >> (used several times in leoImport.py) << Check both parts for @ comment conventions >> (used several times in leoTangle.py) << Compare single characters >> (used several times in leoTangle.py) << Set the default directory >> (used several times in leoAtFile.py)

-- Used (properly) in two different derived files:

replacePatterns (used on purpose in setup.py and postSetup.py)

clonedAtFileNodes: 9

@file ../plugins/add_directives.py @file ../plugins/french_fm.py @file ../plugins/open_with.py @file ../plugins/vim.py @file ../plugins/xemacs.py @file c:\prog\test\multiple.txt @file-nosent nosentCloneTest.txt @silentfile ../doc/PKG-INFO.TXT @silentfile ../doc/README.TXT
This code is extremely dangerous at present.
This completely eliminates namespace polution and also simplifies tree.select.
Nav plugin now warns if icons do not exist.

Leo now destroys the Recent Nodes dialog when user hit the close button at the top of the window.

Leo updates the Recent Nodes panel only if it is visible.
Created new nav_buttons plugin. This plugin adds 4 new buttons to the icon area at the top of each Leo window:

- Back and Forward arrows that work like a typical web browser. Clicking the back arrow button takes you to the last node in the tree you visited. Clicking the forward arrow button moves you forward. The forward arrow button is dimmed unless you have clicked the back arrow button recently.

- The Recent button opens a non-modal dialog showing all the nodes you have visited recently. This dialog is updated cleanly by the tree.select method. You will see this entries change dynamically. There are buttons in this dialog for clearing individual entries or clearing all entries. Double-clicking any entry in this dialog takes you to that node, as does clicking the Go button.

The entries in the Recent Nodes pane do _not_ correspond to the order implied by the Forward and Back arrows. Rather, the most recently visited node is on top, followed by the next most recently visited node, and so on, with the additional feature that nodes appear only once in the list.

- The Marks button opens a non-modal dialog showing all marked nodes in the tree. Marking or unmarking nodes in the tree adds or deletes entries from this dialog. Double-clicking any entry in this dialog takes you to that node, as does clicking the Go button.

I think the Marks dialog will be more useful for most people, for the following reasons: 1. You get to choose the nodes that appear in the dialog (using the Mark and Unmark commands) 2. The nodes appear in outline order.

In addition, hypertext links now enabled by default. Control-click any section reference to go to its definition. I changed the colorer so that it does _not_ underline active hyperlinks in section reference. If a section reference is underlined, it means the definition does _not_ exist. Otherwise the section reference _does_ exist and control-clicking the reference takes you to the defining node in one of its descendents.

Still to do: extending the color_markup plugin so that it underlines hypertext links in comments, """ python strings and doc parts.

It was great fun doing this project: plugins rule! However, I am not really convinced that these dialogs are actually going to get used all that much. For me, gathering nodes together using clones is by far the best way to make sense of a complex project.

Anyway, the code looks robust and fairly complete. I have no plans for any more hacking, though all sorts of additions are fairly obvious, including more buttons in the Marks window.

What I did:

leoColor.py

- Hypertext links now enabled by default. Control-click any section reference to go to its definition.

- Leo no longer underlines active hypertext links.

- To do: http hypertext links in color_markup plugin?

leoCommands.py

- Added c.beadList & c.beadPointer to implement browser-style forward and back buttons. Adding these ivars to the commands class is reasonable because the Forward & Back commands are now official Leo commands.

The logic for the Forward & Back commands is similar to the "bead" scheme used by undo. However, it is completely separate. In particular, these commands are careful to check that a node actually exists in the present tree before going to it! And the Recent Nodes dialog removes nodes that do not presently exist using the v.exists method.

- Added optional updateBeadList param to c.selectVnode. This adds flexibility to how the Forward & Back commands work: I'm not sure that Leo uses this flexibility at present.

leoDialog.py

Added three new classes to support Recent nodes and Marks listboxes: class listBoxDialog and two derived classes:

- class recentSectionsDialog (listBoxDialog): - class marksDialog (listBoxDialog):

leoFrame.py

- Initialized bead logic in frame.__init__. This is a kludge. Beads should be a separate class.

- Added support for existing graphics to addIconButton. (different names for keyword args).

- Added Go Back and Go Forward commands (and related logic) renaming the old commands to Go To Prev Node and Go To Next Node.

leoNodes.py

- Added exists method. This method is used to check (quickly!) whether v is in c's tree. We need this logic because undo may have altered nodes that appear on the bead list.

leoTree.py

- Added support for Forward & Back commands to tree.select after select2 hook.

- Added select3 so that plugins can call their own code following this new code.
Apparaently the zfill string method was added in Python 2.2.2.
@color

I am uneasy about the security implications of having Leo execute script x if it is passed a "script:x" parameter. Perhaps I am being over-cautious.

Anyway, there is no need for this machinery. To create reference file for unit testing call leoUnitTest.createReferenceFiles in the old copy of Leo. This only needs to be done once.

@color

if 0: # Just run this from the reference copy of Leo. import leoUnitTest leoUnitTest.createReferenceFiles()

if 0: # Not needed. It does work, though.

from leoGlobals import * import os

os.spawnl(os.P_NOWAIT,"c:/python22/python.exe", "-i c:/prog/leoCVS/leo/src/leo.py", "script:leoUnitTest,createReferenceFiles")
Using an idle hooks completely simplifies the code, and it means that Leo never slows down to update the area.
Removed the ancient kludge for forcing the height & width of f. The code in leoFileCommands.getGlobals now works!

The reason that Leo was opening small windows without the height & width params is that getGlobals was telling it to do so!

Changed leoConfig.leo/.txt to use 75 and 200 for the initial height and width.
I replaced all the ivars with a dict and several lists. This is much more clean. However, the same idea isn't so good for the commands class: using individual ivars is much simpler. Similar remarks apply to the Prefs panel.
destroyIconFrame createIconFrame insertButtonInIconFrame
This affect the Write Missing @file Nodes command.
The new code uses three passes in colorizeAnyLanguage!

Pass 0: The normal code.

insertWiki picture sets colorer.redoColoring = true if it is ever called in pass 0. This ensures that the following two passes will be called. If the body contains no images, the new code is equivalent to the old.

Pass 1: Insert graphics into the body text.

We do this in a separate pass to simplify the following kludge.

Pass 2: Insert dummy characters and recolor the text.

Images take up a real index, yet the get routine does not return any character for them! We insert dummy blanks in s at the positions corresponding to each image to keep everything in synch. We then recolor the line normally.

This three-pass algorithm appears to be robust. In particular, it handles the following special cases:

- The user can invalidate an image name, requiring a full redraw.

- The user can delete an image itself, leaving the markup unchanged. The image will reappear the next time Leo does a full redraw.

- An image may appear twice in the body text.

- The user may edit any line not containing a graphic without invoking passes 1 and 2.
- Fixed bug: continued """ weren't colored properly when @markup wiki not in effect!

There is now only a single entry point into Leo: the run() routine.

This routine uses an optional fileName arg.
- This supports the @markup wiki directive.

- It can add arbitrarily many directives.
Not yet: user directives for leoAtFile.py or leoTangle.py.
Created "init-color-markup" hook for use of markup plugins.

This allows the colorizer to completely recolor a node when the @markup directive changes.
Continued """ weren't colored properly when @markup wiki not in effect!
1. Leo now scans all .py files in the plugins folder when looking for plugins.

Filenames need not start with mod_, and Leo scans only the plugins folder.

2. Replaced detailed loading message with a summary of the number of files loaded & examined.

3. Added importFromPath in leoGlobals.py.

This encapsulates a lot of details, and makes it unnecessary to alter sys.path.

1. Changed handleLeoHook to doHook.

2. Changed most instances of:

if doHook(...) == None:

to:

if not doHook(...)

This works provided that doHook does not return a "significant" value.
- Converted wiki code to a plugin. No need for config settings!

- Added code to parse wiki stuff in doc parts and comments.
Added the following stub hooks for customizing the outline pane:

drawBox doHook("draw-outine-box",tree=self,v=v,x=x,y=y) drawIcon doHook("draw-outline-icon",tree=self,v=v,x=x,y=y) drawNode doHook("draw-outline-node",tree=self,v=v,x=x,y=y) drawText doHook("draw-outline-text-box",tree=self,v=v,x=x,y=y) drawTree doHook("draw-sub-outline",tree=self,v=v,x=x,y=y,h=h,level=level) redraw doHook("redraw-entire-outline",c=self.commands)

Conceivably it would be better for a plugin just to replace these routines.
I have found this command to be highly annoying and unwanted.
- Load plugins and execute hooks in alphabetical order.

- Created "new" hook. Somewhat useful.

Created "create-optional-menus" hook: extremely useful. The first stub hook.

Early this morning I uploaded the new 4.0 code base to cvs. This is an important milestone in Leo's history. I urge anyone using code on cvs to at least skim through this long posting.

1. Code on cvs is safe and stable

AT PRESENT, the code on cvs reads and writes 3.x files just as before. The code appears stable, which is not surprising because the new code is based on the old. Very few changes were needed to handle the new file formats. These changes are enabled when app().use_gnx is true. app().use_gnx is false on cvs now.

I think the present cvs code is safe and stable. However, I _strongly_ recommend that you assume otherwise: Please make backups of any files or folders affected by the new cvs code.

Most of the work required to read and write 4.x .leo and derived files is complete; there are only a few days work left before the transition to 4.x formats. I do NOT recommend that you enable the new code; some file format changes are in store. You will be warned when the transition to the new file formats happens on cvs. This will be another major event: all sentinels will change.

2. Code bases have merged

The code on cvs merges the 3.x and 4.x code bases. In retrospect, I regret splitting the development. It really didn't help. In any event, there will be no more splits in development.

With the merged code base, one can easily imaging releasing both 3.x versions and 4.0 beta versions in parallel. Indeed, the only difference between the two versions would be the setting of app().use_gnx. Also note that even in the 4.0 final version you will be able to write 3.x files. Just set use_pre_4pt0_file_formats = 1 in leoConfig.txt.

The latest cvs code assumes that leo folders contain the config, doc, plugins, scripts and src subfolders. The new version of the installer script, leo.nsi, found in LeoPy.leo, creates these folders. See the diary entry for 5/12 in LeoPy.leo for a list of the changes made to support the new directory structure.

3. Improved code base & new invariant

I took the opportunity afforded by 4.0 to clean up the old code base in several important ways. First, the new code uses proper Python lists to represent join lists. This greatly clarifies the relevant sections of code, and may even provide a performance boost. Secondly, tnodes now contain both headline and body text. This change creates an important invariant: vnodes with the same vnx's always point to tnodes with the same tnx's. This invariant is vital to resolving conflicts properly.

4. Prototype of graphics & styled text

leoColor.py contains a prototype of code that allows wiki-style markup in body panes. This is important work, because it tests Tk's abilities to handle styling and graphics in the highly dynamic environment that is Leo's body pane. Some details:

- There are a few bugs left to fix, and it is clear that this prototype is a great success. The combination of Python and Tk are easily up to the job. The programming details are interesting, and not interesting enough to discuss here :-) Look at the topic called (Graphics & Styled Text: Wiki format) in LeoPy.leo if you are curious.

- The code that handles the wiki formatting is disabled at present on cvs. To enable it, just change if 1: to if 0: in doWikiText. It is quite safe to do so: the syntax colorer catches all exceptions. The worst that can happen is that some of your text won't be colored properly.

- The present code only handles wiki markup in Python triple-quoted strings denoted by three double quotes. Because wiki markup uses single quotes, it does not seem prudent to allow wiki formatting in ''' strings. Still to do: allow wiki markup in doc parts and comments. This will be easy enough.

- doWikiText at present only handles the following markup:

__bold__, ''italics'' (two single quotes, not a single double quote!) {picture file=<filename>} ~~<color specifier>~~

To repeat: using wiki markup to denote styling and graphics is merely a prototyping expedient. I'm not sure how to represent such information. The great advantage of a text-based specification is that it doesn't change the rest of Leo _at all_. In particular, I had a nice aha: we can use the Show Invisibles command to show or hide the wiki text. When invisibles are hidden, all you see are the _effects_ of the wiki markup. When invisibles are visible, you see the markup plus their effects. No need for extra commands.

- rst is another alternative to wiki format. I'll soon start another thread to discuss the pros & cons. Please don't vote here.

- Instead of the present hand-written parser, Leo should use an "official" parser for whatever markup is chosen. However, this can wait.

5. To do

There are significant additional work to do before 4.0 final. After 4.0 alpha 1 comes out (maybe in a week?), I plan to rewrite the code that reads .leo files. The new code will use an xml parser (probably sax) to read .leo files rather than the present hand-written mess. I have fairly high hopes that sax will significantly speed up the reading of .leo files themselves. Note, however, that such tools can _not_ be used to read derived files because derived files are not xml files.

Edward
This required folder-by-folder copies, so it was potentially error prone.
This cleans up the list of files being read.
These don't change often enough to warrant reading every time.
1. Created config, doc, examples, plugins, scripts, src, test & tools folders. This really clarifies matters.

2. Removed the following files:

- nsi.leo file. This was a duplicate of code in LeoPy.leo. - The following unused .bat files: bdist.bat, go.bat, help.bat, sdist.bat. - The two resource files: leo.rc and leo.res. These are for Delphi!

3. These changes required changes to:

- Actual code. The following commands were affected: - The About Leo dialog (path to icon in dialog). - Tree drawing code (path to icons) - Open LeoDocs.leo and Open leoConfig.leo commands in help dialog. - Initialization in leoConfig.py.

Note: Tangle/Untangle will find tangle_done.py and untangle_done.py provided that the scripts folder is in sys.path.

- The leo.nsi script that creates leoinstall.exe. This required considerable amount of work.

- Paths specified in @file nodes.

- All files in cvs. The attic contains all the old files. cvs contains new 1.1 versions of all code. This is most unfortunate. However, the cvs code started out with the 3.11.1 code base, so 4.0 changes will be visible.
Sometimes the reading: @file messages were not showing up during Open commands. Perhaps the are differences in the events that caused app().log to be set to None, or to be set before the Tk text widget was initialized. Could problems with configuration show up like this? Or maybe it was just a weird Windoze bug...

Added code to force setting of app().log in OpenWithFileName.

If this bug shows itself again I'll add print code to show the following:

- The frame associated with app().log. - The events that set app().log to something _other than_ None.
Saving LeoPy.leo generated 46740 calls to v.threadNext!

It looks like optimizing threadNext would be useful. There seems to be no need to optimize threadBack.
What I did:

- Replaced mFirstChild with mChildren. - Partially replaced mBack and mNext fields. - Assumed the existence of c.frame.dummyChildren array.

I abaondoned this code for the following reasons:

- The new code might be slower than the old code!

- mBack, mNext and mFirstChild will take less space than mChildren.

- Precomputing threadNext() and maybe threadBack() may be _much_ faster.
In 4.0 we must have a single, global (per-outline) tnodesDict so that all joined vnodes in fact use the same tnode.

This dict must never be reinitialized!

*** N.B.: we create the sharing of the common tnode first, then create the join link later, at the end of getLeo2 or other top-level read routines.

This actually may have found one broken clone. It certainly did find a copy of code.
nodeAfterTree was being called too often.

Used same logic to delete tree after read errors that was used to delete tree when using gnx. The old code called doDelete, and that was the cause of huge delays when there were read errors.

Removed the << Handle all status bits >> logic from atFile.read. There is no need for this! The fileCommands logic sets the current node logic.
Eliminated awkward dummy nodes.
What I did:

- Created fileCommands.setAllJoinLinks to create or update all join links.

- The fileCommands.getLeoFile and fileCommands.readAtFileNodes now contain:

self.setAllJoinLinks(c.rootVnode()) c.initAllCloneBits()

** Removed the code equivalent to setAllJoinLinks from atFile.read.

** Removed the code that scans the entire vnode tree for the current & top attributes. The code _must_ set these without rescanning an entire derived file!

- Added atFileNodesFlag=true param to getLeoFile.

- Added partialFlag=false param to atFile.readAll.
More checks would be good.
This was easy to do because most code uses the v.headString() getter.
The first version didn't work, and caused clones not to work!

This could have caused a major mess with Leo.py, but I reverted to a previous copy and I believe no clone links have been damaged.
I'm not sure how an empty filename got passed to this routine, and it did happen.
See the (Rewriting joinList code) project for full details.

Examining the code revealed that the undoReplace code does not work. This is a long-standing bug!

Leo was using the defaultID when creating new gnx's. We must always use app().userID when creating new gnx's!

- Added userID ivar to node indices class. - Removed the id arg to getNewIndex.
We do this to discourage users from messing with such lines.

Leo writes bogus @space "sentinels" following @+v and @+t sentinels, and readLinesToNextSentinel looks for such lines while scanning without calling sentinelKind.

Perhaps readLinesToNextSentinel should be called readLinesToNextRealSentinel ;-)
What I did:

- created nodeIndices.setTimestamp() routine.

This sets the new timeString ivar. This saves a large number of calls to time.strftime. All write routines should call setTimestamp once.

- created assignAllGnx method. The compact/AssignFileIndices methods call this if use_gnx is true.

This ensures that all fileCommands write code assigns gnx's.

Also, Leo doesn't needlessly compute file indices when use_gnx is true.

- Assert's will fail in the fileCommands write code if we try to write a node without a gnx.

- The atFile.write code allocates t.gnx if needed.

This may be needed for the Write @file Nodes command.
I left in the commented out code that reads these fields, just in case we come across old .leo files with them in. This code should be removed completely soon.

What I did:

For reading/writing .leo files:

- Removed gnx arg from createChild. - Removed the code that allocates v.gnx. - Removed the code that reads and writes the vnx= field. - Removed v.getGnx

For reading/writing derived files:

- Removed the code that reads the gnx field in @+v sentinel lines. - Called putSentinel instead of putSentinelAndGnx when writing vnodes in derived files.
This should minimize any pain on Linux systems.
I want to make sure that the proper read code is executed when reading _old_ .leo files when a.use_gnx is true.

I checked to make sure that node indices are resolved properly in all cases. They are because self.tnodesDict is used regardless of a.use_gnx.

I also did a search on .use_gnx and in all cases the code will handle old .leo files even when use_gnx is true.
This makes the code clearer and cleaner. I wrote getT before knowing about dict.get.
This is far simpler than trying to figure out whether to allocate them beforehand.

Added code to do this when writing both .leo files and derived files.
The read code handles both txn and t attributes for compatibility wity old files.
We want to be specific because v elements will have both vnx and tnx fields. The tnx field is about to replace the old tx field.
It sets app().use_gnx = false.
This was a vain attempt to speed up the code. Apparently the problem is elsewhere. However, the new code is much simpler, so I'll stay with it.
This substantially reduces the size and clutter of .leo files.
This should be false if you plan to upload changes to Leo's cvs site.
This command is needed, I think, so that people can continue to use older versions of Leo.
This is required to be able to recognize shared nodes.
The nodes are created by default, and we must be very careful to share tnodes properly.

The present code probably does this, but we must be ever vigilant.
List comprehensions are present in Python 2.0 or above.
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
@nocolor

I have just uploaded code to cvs that supports a new @lineending directive. This 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.

More importantly, I have "cleaned up" the code in the vital tangle.scanAllDirectives, atFile.scanAllDirectives and the scanDirectives global function by factoring out code to global helper functions. Normally this should have been done at the start of a release cycle, not at the end, but I got carried away :-)

So please test this new code by writing all your files with the Write @file Nodes command and the Tangle all command.

I'll delay 3.11 final just a little longer to make sure I haven't done something really stupid, though I have reviewed the cvs change logs carefully (and found a few minor mistakes in the process). I promise this will be the last changes to 3.11 (except for bug fixes) before 3.11 final finally goes out the door.

Anyway, 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)

Edward
These utilities simplify and regularize scanning for the @encoding, @lineending, @pagewidth and @tabwidth directives for atFile.scanAllDirectives, tangle.scanAllDirectives and scanDirectives.

This is a partial solution for the problem of duplicated code in these routines.
skip_long was throwing an exception if there were no valid digits following a + or - sign.
- Enabled ok button only if 4 or more characters. - Message now says that 4 or more characters are required.
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.
This adds http:\\ to url's if needed.
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.
- 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.
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.
This makes the output look much better. Only time will tell whether cvs likes this way...
- Added comments param to handleLinesFollowingSentinel.

- Hacked readLinesToNextSentinel so it accepts the first line even if it looks like a sentinel. This won't handle all cvs conflicts perfectly, but perfection isn't possible anyway...
- wrote readLinesToNextSentinel and handleLinesFollowingSentinel. - added optional nextLine param to scanText to handle look-ahead.
I spent several days on a false start. My first try was to create a new version of scanText called scanGnxText. This dispatched routines to handle 4.x sentinels. There were several big problems with this approach:

- Much of scanGnxText was identical to scanText.

- Using separate routines to do the work meant that ivars had to be used for communication between the routines rather than the local vars.

- Instead of using recursion to handle nested sentinels, scanGnxText had to manage explicit stacks. This was so ugly...

This morning I threw in the towel. Rather than do all this extra work in the name of "elegance" I simply added a few new cases to the old scanText. The result is that all the old code works exactly as before, and the new code gets to use the old tested code.

While I was at it, I made the following changes:

- Renumbered the constants in leoAtFile. Some of these numbers were duplicates, which might have caused problems, though apparently it did not.

- Removed the "implicit child index" logic from scanText. This was never used.

BTW, the write logic always used the old logic as a starting point. The present 4.0 write code was up and running in a couple of hours.

Other notes:

- Setting app().use_gnx to true enables two commands in the Read/Write menu that test the new code. I don't recommend doing this: there are plenty of problems yet... app().use_gnx will be false for all 3.x releases.

- The present code uses id(v) as a dummy global node index (gnx). This will be changed very soon.

- Still to do: the code in leoFileCommands to read and write "mark" elements. This isn't essential, and without this no marks will be remembered in derived files. I must also handle the process of telling Leo the user's cvs id. This will probably involve reading leoId.txt.
I had forgotten to compile leo.nsi.
The children of this node are a test of this.
Changed redraw_now to force_redraw in tree select routines.
This allows time to be expressed as local time or gmttime.
These were long-standing bugs.
This makes the file name easier to see in the Windows task bar.
This fixes several bugs.
Every call to v.unjoinNode was calling c.clearAllVistied.

Fixing this bug will allow me not to use inodes & locations for the foreseeable future.
scanColorDirectives now returns self.language.
@nocolor

1. Put customizeLeo.py on cvs. I don't particularly see why this file should be much more dangerous than any other file.

2. Remove the @ignore directive from the ancestor of @file customizeLeo.py.

3. Remove the warning when customizeLeo.py is written. However, I shall _insert_ a lock that prevents customizeLeo.py from being written unless use_customize_leo_dot_py = 1 in leoConfig.txt.

Leo will write a message to the log if customizeLeo.py is dirty but was not written because of the setting in leoConfig.txt.
Simplified and regularized hooks:

- replaced "event1" and "event2" hooks with 'regular' hooks. * Much easier to understand. - added "iconrclick" hooks. - added "bodyrclick" 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 have c,v arguments now. - "command" hooks now return "undo" and "redo" rather than "cantundo" and "cantredo"

Improved code:

- improved the tracing of hooks in customizeLeo.py. - 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.
The fix was to set the hand cursor in
v.OnEndDrag can be called even though tree.dragging is false.
replaced c.contractVnode by v.contract. replaced c.expandVnode by v.expand.

This was mostly done earlier, but wasn't done completely. Not doing so completely caused exceptions in the promote, demote and import commands.
added raw=1 to all calls to config.get in leoConfig.py.
This can be useful when clones are in the range of different @path directives.
Dirty bits are kept in tnodes, so setting/clearing a dirty bit in a node v does the same in all nodes joined to v.

Also, made setDirtyDeleted equivalent to setDirty, which means that setAncestorsOfClonedNodesInTreeDirty is no longer used. This is a bizarre routine: I have no idea why it should be needed.
Previously saving a new file did not result update the Recent Files menu.

Created frame.updateRecentFiles routine and called it from openWithFileName and all Save routines.
In the preferences dialog the close button is now equivalent to the Cancel button.

While I was at it, I eliminated OnClosePrefsFrame. The OnCancel routine works just fine.
There was a typo in the documentation of chapter 8, "About hooks". In the hook table the "when called" column of "headkey1" and "headkey2" says "before/after *body* keystrokes", instead of "headline keystrokes".

Fixed in LeoPy.leo, LeoDocs.leo and on the web site.
These were easy to do and they may be of real help to some people in some situations.
I have just uploaded a major improvement to Leo's expand/contract routines. This came about as the result of a request by Travers A. Hough.

His comment was that it would be better to have the Expand To Level n commands work only on the presently selected outline: "Having Expand" commands that work only on a suboutline...would most likely be what the programmer really wanted." Indeed, this is correct. I'm not sure why I didn't see this earlier.

As I played with the expand/contract commands it gradually became apparent that the new way of doing things makes a number of existing or requested commands obsolete:

1. I redefined Expand To Level 1 to mean contract the presently selected outline. So there is no need for a Contract Present Node command (!)

2. Expand To Level 2 now does what the requested Expand Present Node command would have done. Moreover, this command eliminates the need for the old Contract Subheads command!

3. Expand to Level 3 now does what the old Expand Subheads command did!

4. With all these commands now gone (without sacrificing anything!) it became possible to consolidate the Expand and Contract submenus into a single Expand/Contract submenu. This is a pretty big win, in fact, because it shows in one place all the shortcuts that cause expansion and contraction.

As requested, I changed the Contract All command to select the topmost ancestor of the presently selected node if that node becomes invisible as the result of the contraction. This eliminates what was in fact quite irritating behavior.

It is truly unusual that a feature request should spark both wholesale simplifications and an increase in power, and that is what has happened. In this area, I think, as perhaps in no other, Leo's way of handling outlines is now clearly superior to how the great MORE outliner worked.
In other words, the Pressing the Enter key in the Find panel executes the Find Next command.
These are just conveniences, so the user doesn't have to check for save, saveas and saveto command hooks.
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.

This is kludgy, and I don't know what else to do.
@nocolor

Read and respond to this message at: https://sourceforge.net/forum/message.php?msg_id=1806338 By: stwerff Open Discussion 12/23/02

There seems to be a problem to find the correct vnode when there is a double comment given:

vnode not found in outline: exp_definitions*)

The finish comment seems to interfere with the @+node:

(*@+node:1::exp_definitions*)

Jurjen

@nocolor

I see. This is an interesting hole. This problem only arises with languages such as html that have paired comment delims _and_ that have no single-line comment delimiter.

The only solution seems to me to remove either just the closing comment delim or both comment delims from the headline text of +-node sentinels. Do you agree? I'll make the change today.
leoFileCommands.put was recently changed, and the following code was accidentally omitted:

elif self.outputString != None: # Write to a string self.outputString += s
Re-enabled the following code in << set path to the full pathname of a temp file using ext >>

a.openWithFileNum += 1
This allows Leo to be used with a black background.
changed s.self to s,self in tangle.os.
The error reporting code is still highly experimental. In particular, encoding errors in headlines may generate many redundant messages as sentinel lines are written.

Here is what I did:

Phase I: get body text working with utf-8. done: accept @+leo,encoding=<the_encoding> when reading derived files. done: always use <?xml version="1.0" encoding="UTF-8"> done: Read and write .leo files using utf-8 encoding. (It's always been this way). done: convert body text to unicode when reading .leo files. done: ensure unicode in body key handler.

Phase II: get find/change working with utf-8. done: don't call repr on unicode strings in es(). done: convert from utf-8 to unicode when reading find/change params in leoConfig.txt done: encode unicode to utf-8 before writing find/change params in leoConfig.txt.

Phase III: support @encode. Test with iso-8859-1 and utf-8 encodings. done: Colorize @encoding. done: Recognize @encoding in various scanDirectives routines. done: Add self.encoding ivars to atFile and Tangle classes. done: Write -encoding=x. in @+leo header done: Write derived files using atFile.self.encoding done: Use unicode for all headline text. done: Read derived files using atFile.self.encoding. done: Add leo_file_encoding to leoFileCommands class. done: Initialize leo_file_encoding when reading .leo file done: Use leo_file_encoding when reading tnode and vnode text. done: Use leo_file_encoding when writing tnode and vnode text. done: Create config.new_leo_file_encoding. done: Create config.default_derived_file_encoding. done: Use default_derived_file_encoding when writing derived files. done: Use new_leo_file_encoding when creating new .leo files.

Phase IV: Use unicode everywhere done: @encoding on import/export done: @encoding on tangle/untangle

Phase V: cleanup done: replace fnW by fn. replace pathW by path. done: assert unicode type when writing derived files. done: remove unused unicode utilities done: remove or replace all instances of xml_version_string. done: create toUnicode and toString utils. done: change test+unicode to toUnicode done: change test+encode to toString done: create isValidEncoding - better error reporting
I don't believe there is a great need for other encodings in leoConfig.txt and I don't plan to support anything but utf-8 encoding in leoConfig.txt.
The fix was to set true = 1 ; false = 0 in leoGlobals.py.
This corrects a hang in the Open With command.
@ Please add "event=event" to the parameter list when calling handleLeoHook() from event handlers.

I'm trying to add a menu item to the popup menu and am trying to use "event1" for "headPopup" to do so.

I need to replace the OnPopup method with my custom method. But I find that I need the event object in order to do so. However, the calls to handleLeoHook (in event handler methods) do not pass the event object.

It seems like a good idea to pass the event as one of the keywords for event1 and event2.
It's actually working, but it's work is being undone by a previous binding.
This was done in c2py.py, leoAtFile.py and leoColor.py.

I actually think this was foolish to do, regardless of how much "cleaner" the code looks. The problem is that it is not at all easy to test. I think this kind of thing should wait until has a full regression testing suite.
What I did:

- es, trace & trace_tag take a variable number of args. - es, trace & trace_tag convert to string using repr as needed. - move init_sherlock to leoGlobals where it belongs. - init_sherlock now takes an echo param. - only trace_tag takes a "tracepoint" arg.
redoMenuLabel and undoMenuLabel must be initialized in the ctor, _not_ in clearUndoState.
The self.oldcursor logic wasn't working reliably.
In most case the "x2" hook is always called, whether or not the "x1" hook overrides the default code.

The only time this can possibly make a difference is when the "x1" hook actually does override, so the issue of whether or not this is a good idea is almost always moot.
@nocolor

The following are real problems that should be cleaned up:

c:\prog\leoCvs\leo\leoApp.py:293: Object (d) has no attribute (testDialogs)

(done) remove app.testDialogs

c:\prog\LeoCVS\leo\leoFileCommands.py:342: No class attribute (getCollapsedOpenTag) found

(done) change getCollapsedOpenTag to getOpenTag

c:\prog\LeoCVS\leo\leoFileCommands.py:525: Catching a non-Exception object (BadLeoFile) c:\prog\LeoCVS\leo\leoFileCommands.py:583: Catching a non-Exception object (BadLeoFile)

(done) derive class BadLeoFile from Execption

(done) c:\prog\LeoCVS\leo\leoUndo.py:134: No class attribute (redoMenuLabel) found (done) c:\prog\LeoCVS\leo\leoUndo.py:138: No class attribute (undoMenuLabel) found (done) c:\prog\LeoCVS\leo\leoUndo.py:320: No class attribute (commands) found (done) c:\prog\LeoCVS\leo\leoUndo.py:394: No class attribute (debug_print) found (done) c:\prog\LeoCVS\leo\leoUndo.py:423: No class attribute (new_undo) found (done) c:\prog\LeoCVS\leo\leoUndo.py:424: No class attribute (debug) found (done) c:\prog\LeoCVS\leo\leoUndo.py:430: No class attribute (old_mem) found (done) c:\prog\LeoCVS\leo\leoUndo.py:433: No class attribute (new_mem) found

These arise because we refer to both self.x and u.x. to do: change self.x to u.x.

c:\prog\LeoCVS\leo\leoCompare.py:425: No class attribute (makeWhitespaceVisible) found

(done) changed self.makeWhitespaceVisible to cmp.makeWhitespaceVisible
We must do

exec s in {}

NOT

exec s in __builtins__

Passing __builtins__ to exec is the _worst possible_ thing to do, because it pollutes the only truly global namespace FOREVER.
What I did:

- added self.trace ivar to trace write logic.

- changed the following sections of putCodePart:

1. << put the line >> Buffered characters to make tracing easier. (reduces calls to self.os) Don't write trailing indentation if not writing sentinels.

2. << handle @others >> Skip the newline after @others if we aren't putting sentinels.
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.
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.
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"):
Completed a major revision of the menu handling code to handle underlined menus. The new code has a chance of working with translated menus.

What I did:

- Added app()./frame.setRealMenuNamesFromTable() convenience methods. - Use underline param to specify underlined characters in menu names. - Translate & to underline index before removing &. - Alt-E conflicts with Edit menu: changed to Alt+Shift+E - Added f.get/setMenu (canonicalize menu names) - Used realMenuNames for undo/redo. - Disabled Can't Undo, Can't Redo.
The lambda functions for callbacks were not working properly. While I was at it I simplified the code
The original bug report:

"The sample code to invoke idle via "Open With" does not work if leo is installed in a path with a blank, for example "program files/leo". On windows, the pathname needs to be quoted in this case."

I did not find this to be the case. However, there were problems with the sample Open With code in customizeLeo.py and I corrected these.
We scanning for the initial state we must make sure that i < old_len and i < new_len.
This was reported by David LeBlanc and fixed with major hints from Chad Netzer.
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.
Use keyword params instead of synonym constants.

- import - removed dont_indent_refs and indent_refs synonyms.

- tangle - removed verbose and brief synonyms to st_dump. - left other flags alone: they are complex.
Added options for @file and @root.

The options to @root are more important (and potentially more buggy). 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.

This is a little tricky, so please pay attention :-)

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.

Only minor changes were made to the Tangle and Untangle code, and the new code appears to work. However, it has not been extensively tested. I would like to get 3.9.1 out the door this week so the latest "official" version of Leo won't crash when PIL or tkIcon are not present. So please report any problems with the new code immediately.
Untangle was warning about a missing section if the section had only an @doc part.
There is a good reason why os.path.normpath does not mess with slashes on Linux, and there is no reason to create another routine to do so.
Used .gif file so transparency info used by tkIcon code. Rewrote tkIcon code so it doesn't try to resize the icon. Created custom 16x16 LeoWin.gif.
The code now uses the list leoConfig.ivars to generate exec statements that do the work. This makes it possible to add ivars to leoConfig.ivars without changing code in leoConfig.py.
It might be beter if the Suboutline Only and Node Only checkboxes were Radio buttons, but then we would need yet another radio button to say "Search Entire Outline", which is a bit pedantic.

Note that Node Only takes precedence over Suboutline Only if they are both checked.
This works just like the Flatten Outline command except it writes only headlines.
The code that checks whether leading and trailing have been supplied must check against None. Otherwise, a leading or trailing value of zero will cause middle_lines to be miscalculated.
The commands now remember the scroll position so the screen doesn't hope around.
Created the long-overdue convertUnicodeToString utility.
This is called from atFile.scanAllDirectives, tangle.scanAllDirectives and utils_rename.
@ test

@nocolor

@ test

@color
This may or may not solve a crash under Python 2.1.
Leo now ensures that u.oldText and u.newText are set to None when using the new undo scheme. This means that Python now has a chance of reclaiming much more memory.
Leo now forces a full update and redraw if there is a mismatch between the expected value and the result calculated from the Tk.Text widget. This is safe, and won't happen often.
This was making it impossible to specify trailing newlines. It also seems to have helped undo.
We can't use after_idle because that completely overloads the system. On my machine a delay of 500-1000 msec between calls would be sufficient to handle most "periodic" tasks.
It's not perfect, and it will do for now.
Noweb section refs and defs _are_ valid in cweb mode! Allow LaTex coloring in cweb @space, @* and @** doc parts.
Cweb prefs panel setting was overriding @language python (!)
This makes it much easier to pass parameters to hooks.
@language pascal @color

private uses
We can only suppress redraws in undo/redo under very special conditions, namely that we are undoing typing in the same node.

The recent code will leave the screen in the wrong state!
Eliminated the self.delim ivar. This can't be set correctly when coloring incrementally.

Realized that full, non-incremental coloring must _always_ be done when switching nodes. Even if we move from one cloned node to another, the context can be different: different @color, @nocolor, @language and @comment directives may be in effect.

Used incremental syntax coloring when undoing. This speed up undo tremendously.
The principle is this: people should see the scripts they are executed.
@color

@ See the actual code in the child node for complete notes.
@ frame.doCommand and frame.OnMenuClick now set app.log, so top() will be reliable after any command is executed.

Note 1: 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. @c

def top():

"""Return the commander of the topmost window"""

# Warning: may be called during startup or shutdown when nothing exists. try: return app.log.commands except: return None
The old code jumped through hoops not to duplicate code. The new code just uses a new doAtKeyword routine.
The key invarient: frame.menus contains _untranslated_ names. The translated names are used only in the label param to the Tk.menu routines.
The menu hooks make it easy to customize menus.

The open hooks make it easy to perform custom actions when opening already-existing files.
Finished convenience routines.

Used createNewMenu to create Leo's menus.
This will allow users to create new menus without creating new ivars, an important generalization.

self.menus["top"] represents the top-level menu. All other menus are represented by their menu name.
This is long overdue:

- put the entire command in a try: except: block. - eliminates the need for return "break" - provides the natural place for hooks.

It was quite easy now that we have createMenuEntries.
At present, only the start1, start2 and end1 hooks are handled, and many more could be added easily.
This was almost too easy :-)
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.
It was moving past the present line if the s[i] was a newline.

This caused problems in the Java scanner, and could also cause problems in other places.
The trailing @ in @q@ "doubles" the first @ in a sentinel, so putSentinel should not do this, and sentinelKind needs no special code to test for sentinels.

The tcweb.py script tests cweb files. Invoke as follows:

python tcweb.py file1 file2...
@ What I did:

- Changed g to geom or glob. - Removed print_stack. - Moved alert, handleLeoHook & unloadAll to leoUtils. - Moved all leoUtils routines into leoGlobals.py. - Removed all instances of "from leoUtils import *" - Removed "script functions" from leo.py: getCommands, topCommands, topCommand. These are no longer used anywhere in the code. - Made the prolog_xxx constants ivars of leoApp. - Made language_delims_dict and language_extension_dict ivars of leoApp.

I have decided to leave the present scheme pretty much in place, except to move the variables in leoGlobals.py to leoApp.py.

This will allow us to say the following:

1. any function f() is defined in leoGlobals.py. 2. any variable v not of the form x.f is a local variable.

We must double _all_ @ signs following the @q@, so, e.g., a @@color sentinel gets turned into @q@@@@@color@>.

This is easy to do in putSentinel, but a bit harder to undo:

1. sentinelKind locally adjusts the string in cweb mode. 2. skipSentinelStart skips both @@ signs in cweb mode. 3. All sections in scanText that deal with variable text must change @@ to @ within that text.
Only rarely does a routine as elegant as wrap_lines come along.
Added return "break" to body and headline key handlers. Added unreachable return 0 to end of findMatchingBracket.
@nocolor

This is miserable code to get just right because there are so many paths through the code. In addition, making informative error messages is a bit tricky too.

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.
The old name was too misleading.
Leo was not warning about orphan nodes in cweb mode.

There was new code in atFile.write _not_ to check for orphan nodes in cweb mode. I just removed the test for language=="cweb".
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.
The old code scanned the moved tree once for every parent. The new code scans the tree only once. This can result in a significant speedup.
This is taken from Python Cookbook. It's not clear how useful this is with Tk programs.
1. Eliminated special-case code (putCWEB) in atFile.write code. 2. Changed directiveKind so it returns noDirective for @space and @c when language=="cweb". 3. Changed colorizer to recognize both cweb and noweb section definitions and references when language == "cweb"
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?>
Changed find to rfind in << Set headline and ref >>
scanAllDirectives just about must be called on the root of the tree, so directives are recognized. Not @first or @last though.
The aha, just concatenate a unique user id with any local timestamp.
This may complete raw cweb mode.
It appears that nobody ever used @delims in @file trees!
Only shortcut pastes worked before.
1. Changed "c" to "." in

methodKind = choose(self.fileType==".c","functions","methods")

2. Added self.rootLine logic to add @root self.fileName when importing to @root nodes.
Previously, Java only had block comments, and the ending delim wasn't being removed from doc parts.
As usual, I am leary of changing the Untangle logic.

To be effective, the @comment directive should follow the @language directive.
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)
A small convenience urgently requested.
It wasn't always completely clear when the command was complete.
atFile.directiveKind returned miscDirective on _any_ line that starts with @.
This entailed using lots of ivars for communication between routines.
A recent change was not writing language names properly (double quotes were ommitted) in the <preferences> element of .leo files.
The new code is much simpler and more rational than the old, without affecting the defaults that are delivered by the module.

In addition, fixed a but that was causing most new params not to be written.
This directive only applies to @root trees. Like @silent, it inhibits untangling.
Added missing comma before the keyword.
Another major reorganization.
This was a major reorganization.
Replaced almost all calls to traceback.print_exc by calls to es_exception.
We call scanAllDirectives on the _parent_ node, so no directives are recognized in the @rawfile tree itself, and directives _are_ recognized in ancestor nodes. atFile::writeAll has new logic that recognizes @ignore in ancestors of @rawfile nodes, but not in @rawfile nodes themselves.

Writing the file was easy; updating the screen was harder. We must redraw the screen whenever a headline changes, regardless of whether a node is joined to another. The reason is that we must redraw the dirty bits of ancestor @file and @rawfile nodes.
Removed v22 var from leoApp.py. Removed ch3 var from getCwebWord() in leoColor.py Removed self and name args from go function in leoCompare.py. Added from leoUtils import * and from leoGlobals import * to leoCompare.py Removed ok from onCompareFiles in leoCompare.py Removed c from setAncestorsOfClonedNodesInTreeDirty in leoNodes.py Removed import leoPrefs from leoAtFile.py Moved unused assignment to top into #if 0 block in open in leoFileCommands.py Removed unused name var from scanPHPText in leoImport.py Bug fix: changed tangle_state to self in several places in root_attributes.__repr__ Bug fix: converted represent_print_bits to a method (It was indented as a method under class root_attributes) Removed language,single_comment_string,start_comment_string,end_comment_string params from st_enter in leoTangle.py. Bug fix (Major?): restored code to return part number at end of st_enter. Latent bug fix: changed self.font to font in tree.setLineHeight. Bug fix: all paths through idle_body_key and idle_head_key now return "break" Removed topMenu var from tree.OnPopup. Removed config var from leoFrame.__init__ Latent bug fix: changed editHeadlineMenu to self.editHeadlineMenu in leoFrame.__init__ Real bug fix: changed editHeadlineMenu to self.editHeadlineMenu in leoFrame.__del__ (This affects how storage is freed) Removed interp var from leoPyShellMain.
Asumming that python and tcl are installed, It shouldn't take familiarity with python to be able to use leo. I want to be able to type

leo foo

and have leo start up on a new [outline] named foo.leo, or open the existing one if it's there.
Leo now does the following:

1. 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 in leoConfig.txt.

2. Leo issues a message and _disallows_ the entry of the character if it can't be represented in the encoding specified in leoConfig.txt.

In practice, this draconian behavior shouldn't be too much of a nuisance, at least temporarily. Note that _all_ characters are valid in 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 so-called character references. This will require some reorganization of the present code, so I don't want to delay 3.7 to handle them.
It is much more convenient to set these to proper defaults than it would be for the callers to do so. Nevertheless, perhaps the caller should do so, but not now...
This makes the present code compatible with Python 2.1.
I needed this clarification.
> # Tk always uses utf-8 encoding.

You may get that impression, but it is slightly wrong. It is more reliable if you pass Unicode strings to Tk, instead of UTF-8 encoded byte strings.

For a byte string, Tk will guess the encoding. If it looks like UTF-8, it is taken treated UTF-8. Otherwise, it is treated as the locale's encoding. Unfortunately, if you ever manage to mix the two, you get byte salad that you can't ever chew. By using Unicode strings to interface with Tk only, you can avoid those problems.

> print `s`,"tk" > s = s.encode("utf-8") # result is a string. > print `s`,"utf-8" > s = s.decode(xml_encoding) # result is unicode. > s = s.encode(xml_encoding) # result is a string.

Since xml_encoding is iso-8859-1, you are making a mistake here. You have UTF-8 data, but you are decoding them as Latin-1. This will succeed, but it will give an incorrect result. It will succeed since iso-8859-1 is an single-byte code where every byte value is valid. That means an arbitrary byte sequence can be interpreted as Latin-1, but for many byte sequences, the resulting string is non-sense (mojibake, as the Japanese say).

> BTW, with out the first encode/decode pair I can take exceptions in > the last encode.

Nevertheless, this is the correct processing. If you have a Unicode object, as originally obtained from Tk, you should encode as Latin-1 using

s = s.encode("iso-8859-1")

Now, for this specific string (u'a\u0102\xdf\xc9\n'), you will get a Unicode error. The reason is that one character (\u0102) is not supported in Latin-1 - this encoding supports only the first 256 Unicode characters.

So, when saving this as XML, the proper representation would be

'a&#x102;\xdf\xc9\n'

i.e. you'll have to use a character reference. Python 2.2 does not support generating such text very well - you'll have to catch the Unicode error yourself, find the offending character, encode it as a character reference, and encode all other characters as requested.

Alternatively, you can refuse encoding a document in a certain encoding (such as Latin-1), and fall back to UTF-8.

PEP 293 (http://www.python.org/peps/pep-0293.html) will provide a mechanism to generate character references more conveniently - in Python 2.3, you can specify

s.encode('iso-8859-1',errors='xmlcharrefreplace')

HTH, Martin
The Find Previous command could not go to previous nodes.
It would be good to have a prefix option too.
This fix was suggested by Rich Ries. The original report follows:

I found the culprit; it's in the node << scan @@ >> that reads:

k = string.find(s,self.endSentinelComment,i)

This means it stops at the FIRST endSentinelComment, which is the "*/". The rest of the line (with the correct "*/") gets chucked. Changing the line to:

k = string.rfind(s,self.endSentinelComment,i)

(note the "r" before "find") seems to fix the problem.
The new code in putOpenLeoSentinel follows the example of putSentinel.
They are simply relative representations of absolute indices, and so are basically useless.
This is a huge step, and I hope it is forward. In any event, the change is simple and reversable.

I am now considering whether to use relative node indices instead of absolute node indices.

These would have the form +n and -n. This would eliminate the cascade of changes when nodes were inserted. The key question is whether the read logic can handle them. I believe it can, with just a single ivar.
Amazingly, the only change required to allow reads and writes of cloned @file nodes was to eliminate the error in updateCloneIndices. I also fixed a bug: set self.errors = 0 in atFile.read.
The upshot: both =pod and =cut must be preceded and followed by blank lines. The simple tests have this.

Added perlpod hack to set_delims_from_string and changed delims to use double underscores in set_delims_from_language.
This affects @path, @file and @root code. This is probably the code with the most separate paths in all of Leo. I would be amazed if there were no bugs here, and it does seem mostly to work.

The getBaseDirectory utility routine implements the relative_path_base_directory option:

"!" means relative to leo.py "." means relative to .leo file. anything else should be absolute path.
This was just too horrible to keep straight, and bodyKeepsFocus should always be true anyway.
It looks like the undo command does not fully undo changes to cloned nodes. Follow these steps to experience this effect:

1) clone a node (same effect whether @file / @root or other) 2) change the headline of either node. 3) Edit->Undo change Headline (or ctrl-Z)

Only the node that was changed will go back to the original headline. The undo command appears to be the problem since it does not properly undo changes to all cloned nodes' headlines.
The new code is much more elegant than the old, and does a better job of reporting errors.

One new restriction has been imposed: at most one shortcut can 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. This should not be a problem.

It is now possible to bind the arrow keys as desired. See the complete discussion in leoConfig.leo.

Note that all bindings are made both to the body pane and the top-level pane. This keeps things consistent.
There was a missing comma.
It was remembering it only if set as the result of a keystroke. The present code is redundant, but what the heck.
1. Use the present body font in frame.setTabWidth.

2. Do not call frame.setTabWidth in setCommandsIvars. Just set the ivar. This fixes the following bug:

"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 redisplay on save must not go through precisely the same body display logic."
This was too confusing to users.
This was done simply by opening derived files and .leo files in "wb" mode.
Essentially I moved the code that updates body text from the @+node logic to the @+body logic.

This can not be completely correct. It breaks verbatimAfterRef

From the forum message:

I have just uploaded a major change to the logic that reads derived files. Indeed, this is the first change to that logic that has ever been made since I first invented @file trees. This change is needed to correct a bug reported by Rich Ries involving referencing a section defined in a grandchild of the referencing node.

WARNING: although this code is similar to previous code, and passes all the tests that I can think of, you should still consider this code experimental for a while.

The problem: Body text was being updated as the result of @+node sentinels rather than @+body sentinels. This was stupid, 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. This was relatively straightforward, and I do have some hopes that everything will once again be rock solid. However, the context in which this code is executing is obviously changed (that is, after all, the whole point), so no guarantees can be given at this point.

Everything appears to work (all files derived from this file appear to be OK), and this code must be considered experimental for now.
At this point there are no known issues with Leo and Python 2.1.

Python 2.1 gave the following warnings when importing Leo:

Python 2.1.3 (#35, Apr 8 2002, 17:47:50) [MSC 32 bit (Intel)] on win32 Type "copyright", "credits" or "license" for more information. >>> import leo

leoFrame.py:197: SyntaxWarning: local name 'self' in 'createMenuBar' shadows use of 'self' as global in nested scope 'lambda' def createMenuBar(self, top):

leoFrame.py:1262: SyntaxWarning: local name 'self' in 'OpenWithFileName' shadows use of 'self' as global in nested scope 'lambda' def OpenWithFileName(self, fileName):

leoColor.py:1367: SyntaxWarning: local name 'self' in 'run' shadows use of 'self' as global in nested scope 'lambda' def run (self):
It was just a matter of binding some unbound vars to defaults.
- The font settings in leoConfig.txt are not active when I open Leo. They instantly activate when opening the Edit|Set Font... dialog, without having to click anything in said dialog. Then, once the .leo file is saved, the problem goes away.
This code improperly removed trailing whitespace, including newlines, from the "middle" text. Clearly this is wrong, regardless of what other effects it might have.

Affected commands are indent, dedent, extract and extract section.
This makes it convenient to update the version number in this file properly.
We now look through the entire tree looking for @root and @file nodes.
single quote colorized text in plain text mode. This was due to missing parentheses.
CVS conflict files produce so many error messages that these errors are useless.
1. Created a new sandbox and updated CVS into it. This did not update LeoPy.leo. 2. Opened LeoPY.leo. There were read errors in leoImport.py and leoUtils.py. 3. Used diff on CVS to determine which routines had been added to these files. 4. Cut and pasted from updated LeoPy.leo to my "real" copy of LeoPy.leo.

This is most annoying, though it is helpful to pay attention to the added code.

The read errors themselves are useless, and I removed them.

I forgot to do an update first, so after I opened LeoPy.leo with Read Outline Only, saving all files with Write @file Nodes reverted Dave Hein's changes. So I reloaded them from the temporary sandbox. This is very bad.
The code is now functionally identical to the similar code in atFile.scanAllDirectives. The old code did not handle relative paths properly in all cases.
This code crashes on some Windows systems, including my XP system. It may work on Win98.
This involved calling readlineForceUnixNewline instead of readline in the atFile:read logic.
Only the last directive was in effect in @root trees. The new code allows node-by-node control.
This routine does a better job of checking the Tk version than the old code in leoApp.py
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.
Removed calls to c.frame.canvas.focus_force() selectThreadBack/Next, selectVisBack/Next.

I have no idea why this code was there originally.
This may or may not fix Rich Reis's problems.
How did I ever live without this?
Changed:

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

to:

bitmap_name = os.path.join(self.loadDir,"Icons","LeoApp.ico")
> I'd love to open a node and be in the same place as when I was last there.
Everything appears to be working properly.
When starting Leo with no arguments the 'NewHeadline' node was not displayed.
os.rename can fail on some Linux systems.
@nocolor

The new code is as follows. This is extremely ugly, and I'm not sure there is any workaround. The problem is that open is really just a stand-in for C's fopen routine, and the mode param is not uniform across platforms.

@color

# 9/1/02: apparently Linux requires w+ and XP requires w. mode = choose(sys.platform=="win32","w","w+") cf = open(self.configFileName,mode)
This will allow Joe Orr to keep track of hits.
The cursor now changes to an arrow when it moves over a link.
1. The link now takes the user to the SourceForge download page. 2. Called os.setcwd(app().loadDir) to set the working directory for the download.
This is still not working, and I am beginning to doubt that it can be done.

Notes:

1. In Tk terminology, bitmaps are 2-color only. Most .bmp files must be considered _photo_ files. 2. I have figured out how to read .bmp and .gif files using image create photo. 3. All tests were run using tcl/tk 8.3.4, and wm iconbitmap has never worked.
It already works in the other panes, but the recommended code crashes tk83.dll!
There were a couple of botches in the new code:

1. leoTangle.scanAllDirectives should not issue any warning when it sees a relative path name in the @root directive. (Which is the only place it will see it). This is not an error.

2. leoTangle.pathError would create an exception when called.
Amazing!
1. Simplified the path logic in the various scanAllDirectives routines.

2. Distinguished between absolute and relative paths. Relative paths do _not_ set the global defaults: they are handled by the file open logic which does os.path.join(default_path,file_name).

The logic is still quite complex, and for the first time the code may be simple enough to work.
This should end most of the confusion.
There are several problems here:

1. os.path.join(dir,file) won't work if both dir and file contain a partial path name. I'm not sure whether this is a bug--there is probably no behavior that makes sense in all cases.

2. Unless os.path.join works above there is no way to see whether the directory is valid.
Removed '\r' characters when reading. The write logic is unchanged, which makes the changes slightly safer.
An arg got dropped out when I added a flag.
Found several bugs while doing so. What else is new...

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, or None for all files. leoImport.importFiles(dir,type)
The new code properly deletes previous entries regardless of case or path separators.
Changed idle_body_key and idle_head_key so the old values are also checked for Unicode strings. I don't understand why that is needed, and after a cut and paste operation it is needed.

Also fixed Tangle's version of the os routine.

Also changed the assertion and converted to unicode in tnode::setTnodeText.
The calls to config.setX in the put routines in leoFileCommands.py were happening _after_ the calls to config.update(). There is not need to call the config.set routines in putPrefs and putFindSettings provided we do the following in write_LEO_file:

c.setIvarsFromFind() config.setConfigFindIvars(c) c.setIvarsFromPrefs() config.setCommandsIvars(c) config.update()
This creates leoConfig.txt
The old code was totally brain dead. If a setting was missing or "ignore" it could never be set!! Sheesh. This is a major bug because _all_ writes to leoConfig.txt go throught this routine.
Otherwise there is no way to change the prefs!
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.
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 only define whitespace tags when in showInvisibles mode.
Added setRevertVars to update revert settings. This is called from __init__ and onOk.
This is consistent with what the Import commands do for the other file types. It's also much safer.
.tmp files disappear On Windows XP after Leo exits!
The new spelling is to limit_directory_search_extension. Note that this affects the valid entries in leoConfig.txt.
Repeated double-clicking (without intervening single-click) caused the selection to move to the left.
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 '"'.
Simplified leoCompare.py and leoTangle.py.
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.
Amazing. All that was needed was some code to force conversion on write, and code in the event handlers to convert "real" unicode to UTF-8. We are talking about 10 lines of code!

There was a problem in leoFind.findNext: it was backquoting the search string when reporting search failures. Removing the back quotes made the message appear properly.

What was needed was simply to do:

if type(s) == types.UnicodeType: xml_encoding = app().config.xml_version_string s = s.encode(xml_encoding)

On the string s gotten from the Text widgets in the event handlers.

As a defensive measure, I also changed the write code in two places to:

try: self.outputFile.write(s) except UnicodeError: # This might never happen. xml_encoding = app().config.xml_version_string s = s.encode(xml_encoding) self.outputFile.write(s)

The old code wrote `s` instead of s.encode(xml_encoding).
Also changed the calling code to check for val != None and val > 0 or val != 0. The only callers of skip_long are the various places that handle the @pagewidth and @tabwidth code. Negative tab widths are allowed, but not negative page widths.
Initializing to something else was preventing a lot of code from being used!

Retested the @path logic. Everything seems good.
These were used only to set the delim1, delim2 and delim3 locals in atFile.scanAllDirectives. Getting rid of these two useless ivars greatly clarifies the code.
This is weird. set_language would set the delims to the default_language delims if the lanuage was "default"
We can't use choose because both branches are evaluated.

self.single_comment_string was never None before because all languages had either 1 or 3 delims. But Java now has only 2 delims to force block comments.
The same bug appeared in leoAtFile.putIndent and leoTangle.put_leading_ws. These routines should not do w = abs(tab_width) before the test. I was confused that n (the desired indentation) might be negative.

Anyway, as a defensive measure, I used ' ' * abs(n) and '\t' * abs(n) in the routines in leoAtFile and leoTangle that put tabs and spaces.

Also, changed n / w to int(n/w) to avoid problems with future division.
1. The botch was this: set_delims_from_language used to return delim1 = start_block_comment_string delim2 = end_block_comment_string if there were only two delims. But the callers thought that delim1 was _always_ the single_comment_string.

This actually never caused problems with @language, because all languages had either 1 or 3 delimiters. However, it appeared when I forced block comments in Java by using only two delims. And it could always have caused problems when people used the @comment directive.

2. Issued a warning and do nothing when no delims specified with @comment. This could have caused crashes.

3. set_delims_from_string no longer returns hard-coded defaults. Instead it returns None. Callers must now beware, but this is the correct action to take.
The change was made to leoFrame.py::reconfigurePanes.
If a name table has only one name, it must be defined like

name = ("x",)

rather than

name = ("x")

This affected the following options:

"read_only" "xml_version_string" "limit_count" "body_pane_wraps"
1. Removed the following ivars. They were never used.

self.singleCommentString = "#" self.startCommentString = "" self.endCommentString = ""

2. Initialized the following ivars to None in the ctor:

self.default_directory = None self.page_width = None self.tab_width = None self.presentLanguage = None self.targetLanguage = None self.startSentinelComment = None self.endSentinelComment = None

This makes clear that they are really initialized in leoAtFile.scanAllDirectives.

3. Renamed leoAtFile.scanAllDirectives.<< Set delims to default values >> to << Set ivars >>. It's contents is now:

if 1: # 8/2/02 self.page_width = self.commands.page_width self.tab_width = self.commands.tab_width self.presentLanguage = self.targetLanguage = c.target_language else: self.page_width = leoPrefs.default_page_width self.tab_width = leoPrefs.default_tab_width self.presentLanguage = self.targetLanguage = leoPrefs.default_target_language

self.default_directory = leoPrefs.default_default_directory delim1, delim2, delim3 = set_delims_from_language(self.presentLanguage)
There were many, 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. As a result, there is no need to write color values to the log window. - Used str(name) rather than `name` to 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.
See the child node for a proof.
@nocolor

The test code shows that all normal cases are handled correctly. Nevertheless, we must prove that Leo can properly read and write @file nodes with nested @others directives.

Writing:

putAtOthers writes all nodes to be generated by @others. inAtOthers returns false if the node has already been visited. Nodes will not be visited when expanding the "outer" or "upper" @others, and will be visited thereafter. Thus, nodes will be written at most once.

putAtOthers writes only sibling nodes and nodes descending from sibling nodes. The expansion of inner @others nodes will never include parent nodes. The expansion of an @others directive at @file node will write all unwritten nodes in the @file node.

In short,

1. all nodes in the "range" of an @others directive are written once and only once. 2. all nodes in the "range" of an @others directive are written enclosed in @+-others sentinels. 3. No orpan nodes are written.

Reading:

The read logic is unchanged. Everything will work as before. In particular, note that @+-others sentinels create trees under nodes containing the @others directive, just as before. The read logic naturally handles nesting!
The bug was testing for ifdef instead of #ifdef at the start of skip_pp_if. However, I thought the bug might be related to something else, so I ended up being much more careful about brace levels in different branches of an #if statement. The result was the skip_pp_part routine.
I think this is most reasonable: we don't wan't people generating files from imported files unless they take some positive action.
This bug caused following defs to be included in the current def if the line ended in a comment that was followed immediately by another def.
I wanted to generate the following if a class declaration is in a separate node:

class x: @others

Instead of:

class x: << class x methods >>

+ << class x methods >> @others

This doesn't work at first: all nodes under class x become orphans. However, the new version of
In order to make the new import code work, I have _experimentally_ allowed a change to how leoAtFile writes code. This is the first such change in about a year.

The old code in leoAtFile.inAtOthers did not write nodes in @others trees if such nodes descended from another @others node. The new code allows such nodes. Apparently the write code handles this case just fine and the old restriction was unnecessary...
Added entries for default target language, outline, promote and demote.
The code now makes sure that c.target_language and related vars are not None. This should fix the problem, and I wouldn't bet my life on it.
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.
The @others directive was appearing before << declarations >> reference.

The bug report and the fix were from Walter H. Rauser.
I used the dis module as a base. It was remarkably easy. All I needed was

1. dis.opname, the names of the opcodes and 2. some code in the dis module to use as a starting point.
The performance of Leo is as I expected. In particular, reading files is the biggest bottleneck, and scanning string.find, isAtFileNode, is_ws, etc. are called frequently.
@nocolor

Bernhard Mulder suggested the following code for adding the path to idle to sys.path. I used this code with only trivial modifications. I retained the previous error handling code in case there are further exceptions...

@color

try: import idle except: executable_dir = os.path.split(sys.executable)[0] idledir=os.path.join(executable_dir, "tools/idle") sys.path.append(idledir)
The fix was to put a call to frame.top.deiconify() in run() right after the call to the frame ctor.

Apparently I only use openLeo.py to open Leo.
The Compare panel is much easier to use than the old scripts in leoCompare.py.
It is remarkable how recasting the code as two classes clarifies and fortifies the code.
@nocolor

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.

Added the config.configsExist ivar to tell whether leoConfig.txt exists (as far as Leo is concerned).
Also added tcl keywords for syntax coloring.
The @pagewidth, @tabwidth and @language directives could not have worked while tangling! Apparently nobody has every used these directives in @root trees!

1. Apparently, skip_long had never been executed successfully (!!) Indeed, it computed the value of a string character by character using (s[i] - '0') which has no chance of ever working!

2. The code in tangle.scanAllDirectives that handles @pagewidth and @tabwidth could not have ever worked. is_special_bits sets the dict entries for "page_width" and "tab_width", but tangle.scanAllDirectives looks for dict["pagewidth"] and dict["tab_width"], both of which will cause exceptions!

3. The code in tangle.scanAllDirectives that handles @language failed to set the self.x copies of the c.x ivars relating to language! This bug may have dated from fairly recent times when I changed the tangle code to use the self.x ivars rather than the c.x ivars.
This affects Flatten Outline and Import Flattened Outline.
While I was at it, I made a few minor changes to leoDialog.py.

1. Added padx and pady padding in dialogs. 2. Added text="OK" param to AskOk. 3. Used center_dialog utility in leoDialog.center.

The leoDialog code is still ugly, but not ugly enough to fix.
I wanted to see if this would corrupt any derived files by interrupting the write. I doubted that this would be so: events do not work like interrupts.

What happened was that the pending event seemed to slow the writing, and all derived files were written correctly.
The answer to the question, "why not just improve compare scripts?" is that people rarely use scripts. A compare window would be much more convenient. And convenience matters here: it is very important that people feel comfortable importing and exporing files.
Found many spelling errors and repeated words.
We can't just keep getting the x,y coordinates from the event, because then we can't stop.
All files now pass. I changed the followoing:

leoAtFile.py: found bug: unbound c in read() leoColor.py: removed import keyword leoCommands.py: removed unused vars from Convert Blanks.
It was amazingly easy to do this. We need only compute self.line_height from self.font.metrics().
Also fixed a but that resulted in Convert Tabs being undoable.
There were several important optimizations:

1. OnDrag precomputes self.numberOfVisibleNodes(). 2. Eliminate all calls to findVnodeWithIconId() in OnContinueDrag.
Including the split direction and split ration.

Leo now uses all configuration settings from leoConfig.txt.
Just putting a call to update() in leoFileCommands.getGlobals() does 90% of what we want. Moving the calls to

frame.top.deiconify() frame.top.lift()

from OpenWithFileName to getGlobals gets us to 99% of perfection. There is a tiny flash when the frame created in leoFrame.__init__ is drawn then withdrawn.

The last 1% is accomplished by withdrawing the newly-created frame in frame.__init__. With this last change there is no perceptable flash when a window is opened. Of course, all callers of frame.__init__ must now call deiconify as needed.

Trying not to create a frame at all until its size is known would be much, much harder, because the frame and commands classes initialize each other.
The bug was in leoFileCommands.getGlobals.

This was a _really_ irritating bug. Now windows open where they were when closed.
This saves the "user" code a lot of work.

Note: if leoConfig.txt does not exist, the default tables in the section called:

<< define default tables for settings >>

in leoConfig.py take effect, so modifying these tables is yet another way of managing preferences.
The present code works wether we destroy the prefs, font and color panels when we close them, or just withdraw them. It is best to withdraw them, because this preserves their location on the screen when we bring them back.

Some things to keep in mind about this code:

1. There must be code in frame.OnCloseLeoEvent to destroy these frames if present. The reason is that these are now non-modal frames, so they could still be open when we close the Leo window that "owns" them. This is true Regardless of whether these frames are withdrawn or destroyed when closed.

2. Since the window are no longer modal, we might try creating them after they exist. In that case, we must simply deiconify the panel, even if we destroy panels when they are closed.

In both cases, we _must_ maintain ivars accurately telling whether the panels have been created or not. These ivars are owned by the leoFrame class. They are created by leoFrame when creating a frame, and set to Null by panel code if panels are destroyed when closed. Since the leoFrame class owns these ivars, a cleaner way would be to create a frameDestroy method for each kind of panel. But that borders on the pedantic...
so code can say getXparam("name") and always get a proper value.
This was easy to do and useful.
This was much more work than it "ought" to have been, due mainly to the fact that Tk.ListBox does not have a callback for changed selection. So the selection becomes active only on a double-click or a change to the size or bold or italics boxes.

The present frame seems to handle all cases properly and without surprise. The code to do so is tricky.
All that is needed is that each Font and Color panel be firmly connected to a particular Leo window. In fact, we must have separate Font & Color panels for each Leo window. The point isn't that we want all these window: in practice there is almost always just one Leo around. The point is that we can eliminate the irritation of modal windows!

This insight also applies to the Preferences panel. It's not so important for the Find/Change window.
This was a _huge_ amount of work. I am quite proud of the result. The present font panel is much more elegant and less cluttered than the examples I have looked at. Indeed, the font panel pack a lot of power in a very small space. I think this is really elegant design.

I spent hours and hours experimenting with different looks. I finally realized that there is no need for radiobuttons: they just add clutter. More importantly, there is no need for an example frame! The entire Leo window becomes an example.
Leo keywords were mostly being coloring under the keyword settings.
Using Ok, Cancel & Revert buttons is _much_ better than Ok, Apply, Cancel because changes are made instantly. The only problem is that the modal dialog makes it impossible to change text. But with just a little foresight the user could create a "test" document beforehand.
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 usual value of the setting is = ISO-8859-1. This effective changes Leo's file format: older versions of Leo will not be able to read the new files.

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

Notice that this also affects what is written to the clipboard.
Used the following to bind callbac:

@color

for i in xrange(len(self.recentFiles)): name = self.recentFiles[i] callback = lambda n=i: self.OnOpenRecentFile(n) recentFilesMenu.add_command(label=name,command=callback)

@nocolor
1. The @comment directive no longer suppresses syntax coloring.

2. 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.
This was a bit of a fiasco. I updated CVS without a real test. Hopefully I'll learn from this.

Anyway, the REM hack is as follows: 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).

I originally inserted a skip_space in sentinelKind where the comment was "Do not skip spaces here!". However, I eventually settled on a better solution, namely making spaces significant in scanHeader. To quote from scanHeader:

"Leading whitespace is now significant here before the @+leo. This is part of the "REM hack". We do this so that sentinelKind need not skip whitespace following self.startSentinelComment. This is correct: we want to be as restrictive as possible about what is recognized as a sentinel. This minimizes false matches."
The "comment" code was looking for "language", not "comment" in dict.
I'm proud of the code: it is simpler, clearer and more general than the previous code.

There were several important breakthroughs:

1. The old divideSplitter routine had hard binding to various panes. This was wrong. The new divideAnySplitter routine uses params for all frames, so it can be general.

2. The key invariant is that self.splitVerticalFlag tells the alignment of the main splitter. Getting clear about this was crucial to making everything work. I could then split the old divideSplitter routine into divideLeoSplitter and divideAnySplitter. Once that happened, everything just worked when I changed self.splitVerticalFlag from true to false in frame.__init__ (!!)

3. We shouldn't ever delete any frames when toggling the split direction. It's just too hard to reinitialize them. The key to making this happen are:

a) the calls to pack_forget and b) creating placeSplitter from the old createSplitter (now createLeoSplitter) code.

4. The key invariant allows us to define onDragMainSplitBar and onDragMainSplitBar instead of the old onDragVSplitBar and onDragHSplitBar. The new routines work regardless of split direction, so we don't have to rebind the split bars when the split direction changes.
Change c.initJoinedCloneBit to c.initJoinedCloneBits.
Added @root node to root of tree and eliminated @language and @others directives.
Importing a Python file containing classes without methods results in nested classes. A workaround is to insert a dummy method in each class.

The fix was to define classIndent and defIndent variables, and to ensure that we break on equality with these values. Also changed code to generate reference to methods only if methods or nested classes were seen.

There is still a minor bug: The following

class A: pass

Puts the pass statement in << class A declarations >>. This bug is not so easy to fix, and it is easy to correct by hand.
Importing the following file creates 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

The fix was in << Create a child node for declarations >>.
Importing the following script

class C: def __init__(self): pass

created an outline with class C appearing in two nodes, something like:

class C: class C: def __init__(self): pass

The fix was to << Append a reference to class_vnode's methods >>.
This fixes a bug involving clone bits, and is much faster than calling c.initAllCloneBits. The code involves c.moveOutlineRight and similar code in the undo and redo logic.
Verified that u.undoReplace will handle all clones properly. c.copyTree simply makes copies of all tnodes after v.copyTree has actually copied the tree. These are really simple and elegant routines.
The new "replace" paradigm greatly simplifies complex undo. The ahas:

1. swap_links is a very simple way of replacing trees. 2. The caller of swap_links can easily redo join lists and clone info. 3. We need to use new tnodes in copied trees. This hasn't been done yet. 4. Undo and redo use exactly the same code.
The aha is this: the import commands are in essence nothing but an insertion of a tree rather than a single node, so exactly the same code will work for undo and redo!
@ It is often useful to print a traceback message when a try block unexpected fails. This can be done as follows: @color

import traceback try: ... except: # traceback.print_exc() pass
This is important, because it means that invalid entries in, say, the Default Tangle Directory preference will no longer abort an attempted read.
This is a little tricky. The general rules are:

1. Settings in the [override .leo file prefs] section of leoConfig.txt override similar settings that exist in the <preferences> element in .leo files.

2. You may cancel this overriding by using "ignore" (without the quotes) as the value of a setting in leoConfig.txt. For example:

[override .leo file prefs] tab_width = ignore

In this example, Leo will use the tab width in each .leo file, or a default value if it doesn't exist.

3. Leo won't write overridden settings to .leo files.

The actual rules are as follows:

1. If a setting is not found in either the [override .leo file prefs] section of leoConfig.txt or the preferences section of the .leo file, Leo uses a default, hard-coded value.

2. When reading a .leo file, Leo will use settings in the [override .leo file prefs] section of leoConfig.txt unless the value of the setting is "ignore" (without the quotes). Case is insignificant when specifying the value of default_target_language.

3. When writing leoConfig.txt, Leo will write settings in the [override .leo file prefs] section of leoConfig.txt if the setting exists in leoConfig.txt and its value is not "ignore" (without the quotes).

4. When Leo saves a .leo file, Leo updates leoConfig.txt (using rule 3) and Leo will write a Preferences setting to the .leo file only if the setting will not be written to leoConfig.txt using rule 3. NB: this rule does not change Leo's file format because all atrributes of the <preferences> element have always been optional.

5. Leo will update leoConfig.txt (using rule 3) whenever the user closes the Preferences Panel successfully.

As the result of these rules, changes made in the Preferences Panel will become permanent immediately if the the setting will be written under rule 3. Otherwise, the change will become permanent when any .leo file is saved.

I believe these rules are flexible and good.
These commands convert an entire tree.
It is important not to change the c. copies of these ivars.
These are actually quite nice to have, and they are possible only because we have a modal dialog.
This makes it absolutely clear what is happening.
Added @language to all @file nodes. This is good style.
Gary Herron suggested this change. This affects a number of commands. Apparently this makes a difference in Linux...
This allows us to remove the c.setIvarsFromPrefs routine. I have left it in as a reminder of what needed to be done formerly.

Removed app().prefs and all references to it.
This variable should _not_ be eliminated! It is needed when reading the .leo file for the first time.
These commands now set self.mFileName only if the dialog succeeds. This is much more natural.
This was a weird one. I have no idea what I was thinking about! Using c.openDirectory makes absolutely no sense. Probably the c.openDirectory ivar should go away!
The whole preferences issue needs rethinking and reworking...
DEMOTE:

Doc: The Demote command makes all the siblings of a node children of the node.

Reality: Not quite: it makes all sibilings WHICH FOLLOW a node into children
LOG WINDOW:

Doc: The Open Log Window command opens the window, and the Close Log Window command closes it. Only one of these commands is present in the Window menu at any time.

Reality: These don't seem to exist.
INSERT HEADLINE:

Doc: The Insert Headline command inserts a new headline as the next sibling of the currently selected headline. If the root headline is selected, this command inserts the new headline as the first child of the root.

Reality: Insert Headline appears to insert a child if the current headline has any children, and it appears to insert a sibling otherwise.
EXTRACT SECTION COMMAND:

Doc: The Extract Section command creates a new node ...

Reality: The menu item is highlighted (no mater what the first line contains), but NOTHING HAPPENS (no mater what the first line contains).

Leo.py works correctly. Fixed docs.
SYNTAX COLORING

Is never highlighted so can't be used

FONT PANEL:

Is never highlighted so can't be used

At long last cut/paste works properly!

1. The way to get the contents of the clipboard is:

s = app().root.selection_get(selection="CLIPBOARD")

The following does _not_ work:

s = app().root.selection_get(selection="clipboard")

2. Added stringIsValidMoreFile and convertMoreStringToOutlineAfter to leoImport.py. These routines just convert the string to an array and call stringsAreValidMoreFile and convertMoreStringsToOutlineAfter.

3. Removed app().clipboard ivar.
Once again, things are easier with Python than I expect. The ConfigParser class is quite nice: it gives a flexible framework for all kinds of preferences. It would be good to move most, if not all, preferences into this framework.

Note: we no longer need to change the format of .leo files to handle new kinds of preferences! Indeed, we can just define new sections of leoConfig.txt!

Leo reads and writes leoConfig.txt. Leo looks first in sys.leo_config_directory. If that has not been set in sitecustomize.py, then Leo looks in app().loaddir.

This assert isn't useful and doesn't have to be true.
Added code to recover the old .leo file if the write fails. We create a backup (.bak) file by renaming the .leo file if it exists. If there is a write error, we rename the .bak file back to .leo. Otherwise, we just delete the .bak file.
This was so easy...It should have been done ages ago.
LeoDocs.leo now matches Leo's web site as closely as possible. At this point, I consider Leo's documentation completely finished.
The solutions to the control-H bug was to call v.edit_text.focus_force() in tree.editLabel().

Adding a call to es() in endEditLabel() makes everything work, at least on XP, but this is too strange. I finaly fixed the problem by having c.insertHeadline call c.endUpdate(false) followed by c.tree.redraw_now(). This is pretty kludgy. Apparently the call to c.endUpdate() was redrawing the outline after the headline had been selected, with the usual bad results.
I removed a binding that resulted in double-clicks in headlines expanding or contracting nodes. You can expand or contract headlines now by double-clicking a node's headline or by clicking its plus/minus box.

This change was requested by Bill Drissel.
I have spent quite a bit of time on getting dragging to work as smoothly as possible. This has been made more difficult because I know of know way of reading the state of the mouse button without generating a mouse-moved event.

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. Continuing to drag the mouse will scroll the outline pane if the hand cursor moves above or below the outline pane. If you release the mouse button while the hand cursor is above another icon, Leo will move the dragged node after the node over which the cursor rests. 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.

As usual, there are a number of details.

1. 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 highly confusing. It seems less alarming to expand and contract nodes explicitly.

2. 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 are not good. There was a noticable delay between releasing the cursors and halting the scrolling. The present code is simpler and more likely to work on all platforms.
Note that tangle.scanAllDirectives already did this.
es() failed if log is None because log.newlines does not exist. The new code alters log.newlines only if log is not None.
Leo takes an exception. It may be because tcl 8.3.4+ is required. The download tcl is 8.3.4.2, but I'm not sure what version is included in Python 2.2.1.
In particular, the theory of operation for Tangle and Untangle was eliminated by mistake several months ago.
There were two problems in this area. First, v.moveToRoot wasn't preserving the tree when a node was moved to the root. This caused nodes to disappear. Second, the code wasn't taking care to change c.tree.rootVnode as needed when nodes moved up or down.

Several important routines had to be changed. v.moveAfter and v.moveToNthChild now are careful to set c.tree.rootVnode if the root changes. I hacked v.moveToRoot and v.linkAsRoot so that they update c.tree.rootVnode only if the oldRoot param is not None. This allows the init code to work properly, and allows the v.moveToRoot routine to properly link in the rest of the tree. The undo and redo code now use the new param to v.moveToRoot.
@nocolor

The new code isn't great, and it seems to solve the immediate problem.

@color

try: # 5/27/02: We can get Unicode strings here via cut/paste. try: self.outputFile.write(s) except UnicodeError: self.outputFile.write(`s`) except: es("error writing:" + `s`)
After creating some clone nodes with the Borland version of Leo, LeoPy.leo was corrupted. Reading the damaged file with Read Outline Only works, so apparently the problem involves the clone indices in the derived files.

The problem was that not all the code in atFile::updateCloneIndices got copied back to the Borland code. In particular, clone indices were not cleared before being reassigned!
Added skip_matching_delims utility function.
Removed extra whitespace between sections.

Fixed bug in convertCodePartToWeb: added code to skip @file line.
This was an attempt (probably futile) to fix a crash in cut/paste logic.
This allows the the last filename used to be the default for the Outline To Noweb dialog.
We generate <<*>>= only if no file name is given in @root or @file. The old code always generated * for @root.

Also, changed << append head_ref >> so that @c generates <<fileName>>= instead of <<*>>= whenever possible.
I rewrote isDocStart.
Moving an outline up before the "root" node hung in v.setDirty.

The fix was in c.moveOutlineUp: we must call v.moveToRoot, followed by back.moveAfter. To make this work, v.moveToRoot must leave the mFirstChild link unchanged.

Fixes were also needed in undo.undo() and undo.redo() to handle this special case.
This fixed a bug that caused Leo to hang when syntax coloring noweb code with the CWEB syntax colorer.
This routine must take into account the kind of file being imported.
There are many details here. The present code handles substantial code well, and looks good.
The green used by IDLE looks really good to me.
The code now purports to work for both CWEB and noweb files.
Outline To Noweb creates << * >> sections when it doesn't know what name to give something. This may be partially correct, or entirely wrong.
Please note that the same mechanisms work unchnaged for both @root and @file trees.
I hacked the Python and Java code (hacks on hacks) to insert nodes to carry @others commands when generating @file trees. The Python code now handles nested classes fairly well.

At this point I'm not sure what is a bigger kludge, the "parsing" parts or the "code generation" parts. I really don't want to do full parsing here, but the present code is pretty wild. OTOH, it works.

Fixed bug in skip_pascal_string.

Split the Import Files command into Import To @file and Import To @root.
Debugged the Import Files command for .py files. The main problems were getting the params to the scanPythonX routines correct: these were just transcription problems.

Fixed 2 bugs in the colorizer: it wasn't handling Python triple strings or C preprocessor directives properly.

Fixed a bug in skip_python_string. It was returning the start of the ending delim, not past it.
It only took an hour. I am amazed this hasn't been done long ago.
Issue the "reading:" message only if the file can be opened. Eliminated the #--- error reading x" message before read errors.
Added a return value to update_def so that skip_body can tell if a node has been changed. skip_body now calls v.trimTrailingLines only if the node has changed.
Untangle was setting the pane changed even when not reporting any changes. This was due to a call to v.trimTrailingLines. Changed v.setBodyStringOrPane so that it calls c.setChanged and v.setDirty only if the new text is different from the old, but this doesn't completely solve the problem.
These are helpful in leo.py because the commands are slower than in LeoCB.
The code mistakenly set part to zero before calling update_def, with the result that the wrong parts were compared. This simple blunder took several hours to find.
Documented new file format, unlimited undo/redo, auto indent, Contract Parent and Sort Siblings commands.
Note: this code is still untested and very likely has major bugs.
I did this to reasssure myself that such changes were benign, and they are: all files Tangle and Untangle as before. Also, @space (or @doc) is needed at the start of all doc parts so that Leo2 will syntax color them properly.

This took quite a bit of time, and I really should do this for all of LeoCB.leo, or alternatively, convert all of LeoCB.leo to @file trees. I'll probably leave this as an exercise for someone else to do ;-) Actually, the proper way to do this is probably to write a Python script.
The code is straightforward.
Fixed << Scan and define an @code defininition >> and << Scan and define a section definition >> in skip_body. The calls to skip_code were setting k, not i. I'm not sure how Tangle could ever have worked before. Perhaps a reversion of some kind.

With these fixes, all files are tangled identically by LeoCB and leo.py.

Removed trailing lines in code part in st_enter. With this change almost all files compare identical except those containing (!newline). Fixed a bug in put_section which caused whitespace not to be properly output after (!newline). With this fix leo.py tangles all files in tangleTest.leo exactly the same as LeoCB!
One bug remains in the Borland version and will probably never be fixed: Preferences settings do not stick to windows. This is a result of using the arg_xxx vars rather than ivars.
It is intolerable to have misleading syntax coloring. The syntax colorer treats @c just like @code. Tangle must do the same.
Made the root arg optional. If present it is a list with one element and is_special_bits checks for @root. We pass a list of one element to is_special_bits so we can distinguish between None and [None].

Removed @owncolor code.
In leoFrame.py: make the font size platform-dependent. In leoTree.py: use os.path.join to join Icons and file names.
''' wasn't handled like """
pychecker is a marvelous tool. It fixes most of the problems that compilers fix.
Caught by pychecker: the call to special_bits was missing the i param. This would cause a fault when executed.
changed mStartSentinelComment to self.startSentinelComment. changed mEndSentinelComment to self.endSentinelComment.
changed writeError to self.writeError.
Changed message to self.message in BadLeoFile.__init__.

Changed param to es in getTnode().
Initialized s to "" in moreHead.

Note: v.trimTrailingLines does nothing(!)

removed v.saveOutlineWithLevel. This uses dvnodes and is old, old code.

removed v.setThreadLinks. This was a bad idea that should have gone away a long time ago.
x Changed all arg_ vars to ivars in scanAllDirectives: arg_present_language --> self.language arg_tab_width --> self.tab_width arg_page_width --> self.page_width arg_use_header_flag -> self.use_header_flag These will affect the @comment and @language directives! x Changed set_root_delims() to self.set_root_delims() x Changed end_sentinel_len to len(end_sentinel) x Changed sentinel_end to self.sentinel_end in scanDerivedFile. x Added c = self.commands in massage_block_comment. (This routine is never used, though). x Removed extra param to match in put_section. x Initialized code = None in skip_body. x Initialized path in untangleRoot before error messages that use path. x Used marked_flag properly in untangleMarked.
A weird one: LeoCB was removing the last character (typically half of a cr-lf combination), probably in the mistaken belief that it was an end-of-file character. Geeze: how do C++ programs ever work when you have to deal with minutia like eof marks and 2-character newlines. Tk has really spoiled me.

I added code to leo.py to ensure that even compressed derived files end in a newline, which will help.
Emailed Guido. He indicated that the fix just involves filenames, so I took a look at it immediately. Used Leo to study the code and made a simple fix.
This fixes a serious hole in Leo: The read logic would crash (report corrupted sentinels) if what follows a reference looked like a sentinel line!
It turns out to be easy to suppress newlines between sentinels. It is a bit harder to make the read logic work properly.

By single-stepping through the Borland code (which also fails when newlines are suppressed) I see that there is logic in scanText that checks for a non-blank line after a +body sentinel! By adding a check to see that the next line is not another sentinel line we can make the read logic work properly.

I wonder: suppose the text after a reference looks like a sentinel? What happens then?
Fixed a bug that for all languages (like Python and Perl) that do not have block comments. The change was to update_def.
Three characters were colored blue at the start of a section def in: <<name>>= The fix was to change k to 2 in one spot.
This is the only undoable command. Now it puts up a dialog saying that.
Optimize Undo Typing by suppressing the "oldText" entry in the dict if the previous entry was "Typing" and the vnode match. This optimization saves almost half the space used in the bead list!

Verified that the Change All and Read @file Nodes commands call clearUndoState().

The "Cut", "Copy", "Delete", "Change", "Convert Blanks", "Indent", "Undent" are now undoable. The only change was a new param to the body key handler.

Created onBodyWillChange event handler. We must distinguish between commands like "Find, Then Change", which must call onBodyChanged, and commands like "Cut" and "Paste" that must call onBodyWillChange. The former commands have already changed the body text, and that change must be captured immediately. The latter commands have not changed the body text, and that change may only be captured at idle time.

Keystrokes in the headline are now undoable.
The code is hardly more complex than the old limited code. In many places it is simpler.

The code now saves only the "optional" ivars that are non-None. This should be an important space optimization for "Undo Typing".

While messing with Undo in the body pane I realized I could simplify idle_body_key a bit. I believe this is important.
alt-v now work in @nocolor mode.
Almost all of the logic is language independent!

Added keywords for c,pascal,perl & python.
skip_doc must test for actual strings, not the return value from token_type.
This was a fix to the syntax colorer only.

Also colored the equal sign in <<name>>=.
Added undoers for Cut/Past Node commands and Sort commands.

Apparently the Copy Node command has no undo consequences.

Added calls to clearUndoState for Read and Import commands. Actually, the Import commands could easily be undone: they are just like insert commands.

Decided not to clear the undo state after Save commands. Do so is confusing and seems pointless. I may revisit this issue when implementing Unlimited Undo.
These are not present in the CVS tree. They should be.

c2py.py, openLeo.py, tangle_done.py, untangle_done.py, __init__.py.
Most problems had to do with changing c.undoX to c.undoer.undoX or u.undoX.

Everything with an undoer now works.

Cut/Copy/Paste Nodes have no undoers. Sort commands have no undoers.
Apparently WinCVS is set up appropriately. Remember to use my corrct password

Did a checkout from the create menu and everything went well!
It is clear that broken clone links can't be mended automatically. That is, Leo can't be made significantly more CVS friendly. That being so, let us consider how to use CVS with Leo.

Our goal is this: we don't want clone links to break when we check derived files into or out of the repository. In general, clone links will break whenever we a read derived file from a .leo file different from the .leo file that created it. So to meet our goal we must obey the following rules:

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 file the _generating_ .leo file (for the revision as a whole and for each derived file in particular).

2. When we check out files from the repository we must check out the generating .leo file in addition to the derived files that we are checking out.

3. When we check in files to the repository we must check in the single .leo file that generated the derived files that we are checking in.

4. When CVS detect update conflicts in derived files, developers must resolve those conflicts in a "merged" .leo file. All derived files that are later checked in must be derived from this .leo file. When creating this merged .leo files, developers should take care to retain clone links.

These rules make a virtue out of necessity. I always envisioned that update conflicts would have to be resolved in .leo files rather than derived files. What I now see is that this is the also the only way to prevent clone links from being broken.
Just after I completed the Mending Dialog I realized that the concept behind this dialog was fatally flawed: cloned nodes must have the same structure!

Indeed, at the time the Mend Clone dialog is put up, the user would have no way of knowing whether any joins made sense. Conceivably Leo could test for structural simularity, and in that case this dialog would be useless. Note: many read errors arise from structural mismatches; clones could never be mended properly in those cases.

About the only situation in which mending would be possible is if the only differences between trees were outline differences. This suggests that we don't test headline text at all and just rely on structure identity.
This dialog returns a list of pairs of names. It is up to the caller to associate the names with vnodes.
Moved these out of the frame class: the undo class also uses them.
We want to remove only the leading whitespace present on the first line, not all leading whitespace!

Also, the Extract Section command was deleting the section reference line.

I could have sworn I tested these commands. Apparently not.
It appears that DnD will be straightforward: probably less then 100 lines of code!
Removed "blank" and "tab" tags on entry. I thought I did this! Anyway, it remains to be seen whether this has any effect on the "disappearing" selection bug.

I did some perfunctory tests on the various find/change commands and everything appears to be working well. We shall see...
Made headlines longer in tree.headWidth(). Sometimes the right edge of a headline was being cut off. This must be a Tk bug, and the workaround is easy enough.
Cleaned up the code in several ways. Some of these have been bugging me for a while.

Yesturday I realized that the proper time to clean up code is just _after_ a release. That way the new code will be thoroughly tested before the next release.

c.tree.currentVnode -> c.currentVnode() Used keyword.iskeyword() in leoColor.py to test whether a word is a Python keyword. Used string.endswith() in Import code. Renamed is_c_word() to match_c_word() to be compatible with the match and match_word names.
This is used in v.sortChildren and in leoImport.sort testing routine. Apparently the latest code is about the best that can be done. Passing a param to the list.sort() routine might seem elegant, and apparently it is slow. Also, it is not clear how to pass the "n" param to the sort routine. I might challenge the net to do better, but the present code works and is fast, general, elegant and clear.
Experimented with using Python's inspect module. However, it doesn't provide nearly the level of parsing needed for Leo's import commands. We need access to the entire parse tree. It is probably possible...
Removing from leoUtils import * from reload_all() will cause problems. The only workaround appears to quit Python when modifying leoUtils. Sigh.
This is a really useful command!
This is quite tricky code. To capture changed values we typically must set a commands method that calls an idle routine. We can't capture the values by binding Button clicks because values haven't changed yet, or so it seems.

Anyway, the preferences panel settings now appear to "stick" to the presently active window.

The print_prefs routine was vital to sorting out what was happening.
Now the real work begins.
This will serve as a start for CVS.
Added commands handler for all radio buttons. There was a timing problem: it took 2 clicks to set the current language. Also added code to recolor the body pane immediately.
It is vital that @file nodes remain dirty until they are written without errors. Otherwise, a Save command will succeeed without warning, and a later read may cause the dreaded "clones have been cleared" message.
The settings in the Preferences panel now "stick" to the current window. This turns out to be very important for Tangling: I ran into serious problems with Tangling to the wrong directory when more than one window was open.
This turns out to be more useful than Sort Children.
All the "Tangle" Constants must be defined in the same place. It turns out that leoUtils no longer use any of these constants anyway, so I moved them to leoTangle where they have always belonged.

scanError() is now the only part of leoUtils that uses Tangle data. It accesses the commands instances using the top() function. A kludge...

In any event, when I split them I also defined some new values, so values from leoGlobals.py overlapped with values from leoTangle.py. It is a good thing I caught the problem now...
Moved the tangle constants into the tangle class.

Changed header to self.header in an error message in scanAllDirective.

The source of the "mismatch" in Utils.h was that somehow I inserted @c into the root of Utils.h, which should produce errors, and eventually it did.
I reset the default tangle directory while doing regression testing, then saved LeoPy.leo. This destroyed files LeoPy directory, I'm not sure how, so I had to revert. All looks well now.
There was a problem with a global change command. I'll have to do a full regression test of Tangle. This would be a good time to use the new whitespace utility routines.
This was the result of a Change All disaster.
We have to update s_text as well as c.body so that another call to search on the same line will find the updated text.
Newlines now set the dirty bit.

We increase auto indent after a colon. This should happen only for Python mode...
Once again, I am astounded at how easy this is.
Convert Blanks now converts the entire body pane if there is no selected text.
It is doubtful that leo.py will ever support a Print command directly. Rather, users can use export commands to create text files that may then be formatted and printed as desired. Tk does provide print support.
Only blanks and tabs are shown, and they not very well.

What we need is a font that shows blanks and tabs as descent symbols for blanks, tabs and newlines.
The key is that two things have to happen:

1. Menu items should be enabled only when focus is in the canvas pane. 2. Bindings for for the outline commands should be added only to the canvas pane.
A useful convenience method that makes it much clearer what is going on.
Sherlock attempts to read the file SherlockArgs. If found, each line is assumed to be a separate sherlock argument.

However, the main improvement is defaulting empty arguments to "+*". This makes tracing as easy to use as print statements.

Could trace accept multiple arguments, just like print? No: we would lose the ability to specify tracepoints by default. There is no need to duplicate print!
Very easy to do, compared with other systems.
This should solve some compatibility problems.
Alt-1 refers to Alt-Button-1, not Alt-Key-1 (!)
Changed frame.put routines so that they return during quitting.

Also removed trace statements from __del__ routines.
We must be careful to enclose all find/change code in c.beginUpdate/c.endUpdate(false) so a redraw after the call to tree.redraw_now() in find.showSuccess won't deselect the headline text.

It would be elegant to cancel any pending redraw in tree.redraw_now, and that does not seem to work.

Because we don't always redraw the screen, we must update icons "by hand" by calling c.tree.drawIcon.

It is very tricky to compute self.in_headline correctly.

Perhaps a more sensible approach would be to have the tree.redraw routine save and restore the editing status and selection of the current headline. However, the present code works and minimizes redraws, so there isn't much incentive...
The idea was that if all drawing happens at idle time we could do the following:

Eliminate all c.beginUpdate() Eliminate all c.endUpdate(false) Replace c.endUpdate() with c.redraw() Replace c.endUpdate(flag) with if flag: c.redraw()

However, the find command needs to _prohibit_ drawing, and there doesn't seem to be a clean way to do this, so using c.endUpdate(false) seems to be needed.

In other words, without c.endUpdate(false) we would need c.cancelDrawing(), and this doesn't seem any cleaner than the present code.
I messed things up when I "optimized" the redraw code. The Find code must redraw the screen _before_ making selections in the headline--otherwise the headline becomes inactive.
Altered the Cut/Copy routines so they set Leo's internal clipboard (app().clipboard). So to cut from LeoCB to leo.py do the following:

Copy the tree in LeoCB. Paste the tree into empty body text of leo.py. Select all the body text and paste. Move to the tree view, and do a Paste Node.

Yes, this is very clumsy, but how often do we want to transfer information between LeoCB and leo.py?
This is defensive programming. Only a few event handlers override body handlers: These include Control-I, Control-T and Control-D.

Exception: the command handlers for cut/copy/paste must _not_ return "break"! The actual work is done by the Tk.Text widget.
Now that event handlers return "break" they can do things involving focus that they could not do before.
This is done in frame.createAccelerators(). For the first time leo.py handles keystrokes properly!

We could override _all_ body pane keys, and this would be dangerous until all event handlers return "break." Indeed, doing so would create duplicate bindings, one for frame.top and one for frame.body, so without a "break" the command would be done twice!
Event scripts need only return the string "break" to inhibit all further event processing! This is what I have been missing all along, and it was staring me right in the face. Sheesh.

This eliminates the need for several kludgy workarounds: The double-click event handler no longer needs to wait till idle time. The c.moveDown routine no longer needs to call c.select.

Another breakthrough. Way down at the bottom of the Tk.Text documentation I discovered a list of all the key bindings defined by default in Text widgets. This includes Control-O, which explains why body text sometimes had blanks lines inserted at the top. (Amazingly, Control-O never seems to have split lines, purely by luck.)

I can now use control-I to insert nodes again!
This is similar to the double-click logic!
We must compute the word to be selected before the Tk.Text widget does its default double-click handling, then actually select the text at idle-time.
The code was easy to write and worked the first time. I love Python. I chose to ignore case in the comparison.

A more elegant solution would be to define a v.__cmp__ method, but the present code is clear enough and works.
Fixed the Tangle Marked command. It had numerous problems.

The code that executes Leo_done.bat and Leo_un.bat didn't work. Now these options specify Python scripts to execute, and these scripts are passed a list of filenames that were tangled or untangled.
Created go.bat and openLeo.py. These simplify the process of starting leo. Just open a console window and type go.

Added "c:\prog\LeoPy\LeoPy.leo" as default to filename argument to leo.open().
The whole-word search only fails if there are two consecutive "in-word" characters at the beginning or end of the supposed match.

Added some logic to make suboutline-only searches work correctly.

In the process, discovered a bug in is_c_id. It was testing for blank instead of underscore!
tree.select now calls tree.recolor_now instead of tree.recolor. The result is much better than before.

Did add the tree.updateScheduled ivar and logic. This ensures that only one redraw ever happens at idle time. It seems like the body text is drawn much faster now. This may have been a big performance bug!

At present the performance of leo.py seems excellent.
When the "Move Down" command did nothing the control-d ate the character at the cursor! The workaround is to have c.moveOutlineDown call c.selectVnode(v) even if nothing happens. This forces the body text to be restored.
c.makeVisible and even c.beginUpdate/c.endUpdate can be eliminated! Indeed, the tree class can work as follows:

1. All ancestors of the current node are expanded at the start of tree.redraw. 2. The current node is scrolled into view at the end of tree.redraw.

We want to do the expansion of nodes before drawing so offsets are computed properly. We must do scrolling after idle tasks are complete so Tk.Scrollbar.get() will return proper values.

Note: we still may want to inhibit drawing even though all drawing happens at idle time. We don't really need begin/endUpdate to do that: just keep a flag and call c.redraw() only if the flag is true.

I'm not going to eliminate c.beginUpdate/c.endUpdate just yet.
Eliminated redundant error messages. All path problems now generate an error. This seems safest.
tree.makeVisible and tree.scrollTo are called before the tree is redrawn, so we can't get accurate measurements from the canvas class. I created tree.yoffset() and tree.lastVisible() to get proper measurements. The final code is deceptively simple; it took several hours to get it all right.

This is an important step forward because now the screen is drawn properly during interactive search commands. It would have been intolerable to release Leo with only partly functional Find commands.

There are still "policy" questions about when and how to scroll. The present code doesn't work smoothly in all cases. It may be that the Tk.canvas.yview routine doesn nothing in certain unexpected situations.
I have spent several pleasant hours preparing setup.py, manifest.in and sdist.bat.

Create a source distribution (.zip file) by running sdist.bat from c:\prog\LeoPy.

I have not been able to create a .gzip file yet.
There was an assignment kind = kind in the section << set kind for directive >>, so @chapter, @section were never recognized. Amazingly, this blunder did not seem to affect anything else.

With this this bug fixed, all files in LeoCB.leo are tangled the same by LeoCB and leo.py, except for the first line and whitespace. The first line is different because LeoCB puts slashes differently than leo.py.

When ignoring only trailing whitespace and blank lines, we see that LeoCB and leo.py differ in how they output (!newline). LeoCB outputs a single space before (!newline) in put_section(), so this is what leo.py must do as well.

With this bugs fixed, the only differences between LeoCB.leo and leo.py involve leading and trailing whitespace and blank lines. Tangle works! Actually, leo.py now does a better job than LeoCB, for the following reasons:

1. leo.py does a good job of deleting trailing blank lines in sections, and this should simplify Untangle. 2. leo.py does a better job of putting leading whitespace than LeoCB, because leo.py more accurately computes the effective width of leading whitespace than does LeoCB.
This allows us to open a file when leo starts. From the Python interpreter do the following:

import leo fn = "c:\prog\LeoPy\LeoPy.leo" leo.open(fn)

The open script is the companion to the run and go scripts. leo.run() just runs leo. leo.go() reloads all modules before running. For reasons that I don't fully understand, leo.go() does not always handle leoUtils properly: it is sometimes necessary to do

from leoUtils import *
Actually, clicking on another node should probably reset the "wrap_node", but it doesn't. Only changing something in the Find panel does that.
Tested paths when Leo invoked directly. Tested explicit paths in @file nodes Tested paths in prefs panel. Tested paths in @path directive. Tested no path anywhere.

As a result, changed writeError by error in atFile.scanAllDirectives.
frame.defaultDirectory was never being used.
It appears that putting the following in app.finishCreate will work:

import leo loaddir = os.path.dirname(leo.__file__)

Apparently we can not reference leo.__file__ reliably until leo has been completely imported. The workaround is to do so in app.finishCreate. This is a bit mysterious, and it appears to work.
The new path philosophy is as follows:

1. Leo will not call os.chdir except possibly after file dialogs that are not cancelled. This will leave the file system's current directory (returned by os.getcwd()) unchanged by tangling, importing, reading or writing.

2. Commands that access files (tangling, untangling, reading, writing, importing) call either os.getcwd(), atFile.scanAllDirectives or tangle.scanAllDirectives to set appropriate ivars indicating what the proper default directory will be.

3. These commands open a file as follows, where self.default_directory is set in step 2:

fn = os.path.join(self.default_directory, fileName) f = open(fn,...)
@file trees were not being marked as dirty when cloned headlines outside those trees were changed. This definitely would have caused errors while reading @file trees! Fixing this bug is a big step forward.

The fix was to to have v.setDirty() ensure that all cloned nodes are set dirty and that all ancestor @file nodes are set dirty. I eliminated v.setDirtyJoined() entirely, and eliminated all calls to v.setAncestorAtFileNodeDirty() outside of v.setDirty(). The result is much safer, cleaner and simpler code.

As a side effect, nodes now become dirty when they move.
leo.py used to call chdir from many places to indicate which default directory should be in effect. That was wrong. From now on, leo.py will call chdir only file dialogs return without being cancelled, that is, for Open, Save and Import commands.

Otherwise, directives will set ivars, and the code will compute full path names using os.path.join(directory_ivar, pathname). The neat thing about this is that if pathname already contains a full path name it will override the path in directory_ivar.

For example, the Tangle code now writes the final file like this:

file_name = os.path.join(self.tangle_directory,section.name) ... # tangle into temp file update_file_if_changed(file_name,temp_name)

This works properly whether or not section.name contains a full path name.
If the present node is expanded, the Paste Nodes command now pastes the pasted node as the first child of the present node, rather than as the next sibling of the present node.
trace(a) prints a if s is a string and executes a() if a is a function.
The symptom of the bug was that it was taking a _long_ time to close windows after doing many interactive find commands. Actually, it was redrawing the tree that was the real problem.

Disable the recycling of nodes made no difference. By removing code I isolated the performance bug to the call to tree.redraw in find.showSuccess(). This was very good news, as it meant that the bug was in the tree code rather than in Tk or Tkinter.

The bug was caused by "extra" links to the Tk.text widgets in the vnode, specifically, the v.box_id, v.edit_text_id, v.icon_id and v.box_id ivars. These links apparently created permanent references that prevented widgets from being deallocated when the tree was redrawn, so a large number of widgets and their binding had to be deleted when the tree was deleted.

The fix was just to eliminate these vnode ivars entirely: they were not being used! The v.edit_text ivar could not be eliminated, so I added a call to self.edit_text.destroy() just before reallocating. Perhaps just moving the edit_text would be better than destroying and reallocating it, but that wouldn't be so easy because of the call to self.canvas.destroy("all").
If a clone problem is reported, the @file node is not marked dirty again, so if we save the .leo file and then quit the problem will still exist in the derived file.

This happened in Borland Leo, so it is clear if it is a problem in leo.py.

I recovered by doing a Read Outline Only.
Moved Tangle utils into Tangle leoTangle.py. These should be methods because they deal with tangle ivars.

Separated utils that call scanError from other utils.
This happened after deleting a clone outside of the tree for @file leoColor.py. I am not sure of the exact sequence. Perhaps LeoCB was involved.
This was pretty easy. I think underlining hyperlinked section references looks good.

Still to do:

1. We need browser-style navigation: forward, up, back, home.

2. We may have to change things for Leo1.
I finally realized why c.redraw and c.endUpdate wiped out the edit status:

1. tree.redraw does not preserve the edit status (it probably should) 2. tree.redraw only causes queuing of the redraw. It doesn't happen immediately.

This was causing problems in find.show_success. My quick fix was to create tree.redraw_now, and then to restore the status as needed. Another, probably better, way would be to have tree.redraw restore edit status. Duh.

However, in this case tree.redraw_now is exactly what is needed, so I feel a bit less stupid...
There are a huge number of details to get exactly correct. It appears that the code is close to working.
I finally realized that v.edit_text is defined only if v is visible on the screen, that is, only if all of v's ancestors are expanded.
Previously, cloned headlines were not updated in unison. This could have been the source of some apparent "lost data". It is remarkable how complex this logic is.

I finally chose to do a redraw in tree.endUpdate() to force headlines in synch. We can't typically just do a redraw() in idle_headline because that messes up the editing state of v.edit_text.

I also fixed a bug in idle_body that caused control-Q to call c.setChanged() again. The new code just sees whether the code has caused the text to change: it does not need the value of ch to do that.

idle_headline also now handles all details of marking nodes dirty. This used to be done partly in undimHeadline.
I made terrible blunder: I eliminated a commands ivar that was involved in writing, so the write failed and wiped out the .leo file. Since I didn't have a recent backup .leo file, I lost todays notes.

Let this be a lesson to myself:

1. Backup .leo files _are_ useful for backing up ouline only data. 2. Make backup of entire LeoPy folder when there are problems. I came very close to all of today's programming work, which would have been hard to bear.
We don't need to keep track of the status of control or alt keys! Instead, we let idle_body_key and idle_head_key compare the old with the new value. They do nothing on a match.

An important point: idle_body_key must make sure that v == c.currentVnode. If it doesn't make this check we will lose data when the user types Control-K to create a new node.

The new code also takes care not to allow any newlines in headline text.
This code is _vastly_ superior to the Borland code, for several reasons:

1. Unlike the Borland code, the Python code uses no "state" variables. This not only greatly simplifies the code, it makes it possible to restart incremental searches after the user has changed nodes or changed postion in body text.

Instead of using state variables in the find class, the code uses the c.currentVnode and the "insert" and "sel" tags of the Tk.text widgets. This automatically ensures that the state of the search matches the state of the outline and body panes!

2. At last! I know how to restart incremental searches without using state variables. The trick is _not_ to call c.endEditing() in the setup routines, so set_in_headline() can use c.tree.editVnode to see if we are editing a headline. If so, we start there. OTOH, select_next_v _does_ call c.endEditing() and c.editVnode() to force c.tree.editVnode to have the proper value.

3. Leo now uses Tk.Text widgets for both headline and body text. This eliminates all the special case code that selects between headline and body searches. Moreover, the same regular expressions are now used for searches in both headlines and body text. I doubt I could ever have produced bug-free code without this simplification.
This hugely simplfies the find/change commands, and makes them more capable.

This also solves the "Control-K" problem in headlines.
The ensure_extension routine was adding a .leo extension, thereby defeating the test for an empty file name.
This code is much cleaner than the Borland code. The search commands keep almost no state variables now: we always search from the present location. This hugely simplifies the logic, and makes the code do what is expected more often.

In particular, is no longer any "first_v" or "first_position" state. The various find and change commands will save the present state on entry, and restore that state if no finds are found. This "local" save/restore makes much clearer what is happening.

Still to do: searching in headlines.
The save message immediately showed a Save bug. After doing a Save To, a following Save command used the Save To file name! This explains why some saves "didn't take".
This probably should depend on the size of the window, and at present it does not.
I have a new toy: Python's exec statement. This allows one to have all the flexibility found in tcl scripts. For example, one can treat the name of variables as variables themselves. Building statements from strings isn't quite as simple as in tcl, but the effect is exactly the same.

The added flexibily is useful when loading and saving many variables, as in the prefs and find code. For example:

ivars = [ "tangle_batch_flag", "untangle_batch_flag", "use_header_flag", "output_doc_flag", "tangle_directory", "page_width", "tab_width" ]

for var in ivars: exec("self." + var + " = c." + var) # self.var = c.var

This exec statement expresses the pattern directly. Moreover, once the ivars list is defined many similar exec statements are possible. Using the % operator might be even clearer:

exec("self.%s = c.%s" % (var,var))
This is tedious code, and Tk makes it much simpler than with other systems.
Replaced global with app().idle_imported.

Replaced global with fileCommands.. This fixed a big memory botch: previously a new dummy vnode was allocated for every vnode created!

Replaced es_newline global with app().log.es_newline, i.e., frame.es_newline. This is better than before because each log stream has its own count.
These allow us to reclaim memory when closing a window. It also showed a that we are wasting lots of space allocating dummy vnodes in the read logic.

With these routines in place, all __del__ routines are eventually being called, which indicates that all circular references are being destroyed by the destroy() routines. Note that it is _not_ necessary to do any of the following: del v, del tree, del commands or del frame. Setting the pointers to None is enough.

Added tree.vnode_alloc_list. v.__init__ adds items to this list. tree.destroy removes items from this list. This is an elegant and powerful solution: all vnodes will be reclaimed eventually, and we don't need to worry about reclaiming them while the window is open. In particular, this avoids all problems with undo.

Added two new app() ivars to control memory management and tracing. self.deleteOnClose # true: delete all frame objects when a frame closes. self.printDel # true: enable prints in __del__ routines
The frame dtor is never being called because (I think) of circular references throughout the vnode, commands and frame classes. There are several possible approaches:

1. Ignore the problem completely. This makes a lot of sense, for several reasons. A: Memory is not an issue, and reclaiming memory at termination time doesn't help! B: Memory reclamation can legitimately be called a Python implementation issue. C: It takes time and memory to reclaim memory. D: Errors in reclaiming memory can lead to intermittent crashes.

2. Create destroy() routine for vnodes, tnodes, commands and frames. These routines would eliminate links between nodes, thereby freeing Python's references. The frame.destroy() routine would also call self.top.destroy() to force the closing of the frame's window. The destroy() routine would also do a del self to reduce the reference count of the object.

Note: deleting vnodes and tnodes is tricky because of undo. By far the safest course of action is never to delete these nodes until a window closes. The easy way to do this is put every vnode in a vnode allocation list, an ivar of the tree class. Calling tree.destroy() would do the following:

for v in self.vnode_alloc_list: v.destroy() del v

With this structure in place, the __del__ routines would do nothing. We could insert a print statement in the routines to show when the routines are actually called. This would indicate whether circular references remain.
The old code marked the loading headline as changed. Added c.loading ivar to disable update of window title during loading.
This happens when a save is quickly followed by a quit. I can get this error when in the Python window, which is reassuring, I suppose.

frame in app().windowList trick doesn't work: very weird.

I enclosed the code in idle_redraw() in a try/except block. I'm not sure whether this will cure all problems, but it seems like it should.
The old code never got around to deleting the frame, tree and commands objects.

However, del self just decrements the reference count; it does not guarantee that self.__del__ is called. In fact, it doesn't seem that __del_ is _ever_ called! So much for cleaning up memory.

There may be a bug here, or perhaps this is just a weirdness of usint Tk or Tkinter. At this point I haven't a clue.
This is a strange one. The problem happens when a save is quickly followed by a quit. It appears that there is a fault in idle_redraw(), even when the code is locked out with if self in app().windowList. This is very weird. Even stranger is that the error traceback never happens when the Python window is opened first (so it stays open).
Tested c2py.leo1to2() on LeoCB. The script now works on both Borland and leo.py.

There is a problem with the v.commands method on Borland. The workaround was to pass the value from top() to convertLeo1to2(v,c).
Once again, I am making faster progress with Python than expected. Amazing.
I just imported idle and voila: the IDLE IDE is available!

I did have to add r"c:\Python21\Tools\Idle" to sys.path. I also removed the wxPython stuff from the path: they interfered with the include.

I added a few top-level functions to leo.py that were described in the scripting documentation.

I added the public commands and vnodes methods described in the scripting documentation. Several of these routines had names that conflicted with ivars. In those cases I changed x to mX. The changed vnode ivars were back, firstChild, headString, next and parent.

I also added several vnode and commands methods that were described in the documentation but that did not presently exist. Like c.findRoot, c.currentVnode, etc.

The script c2py.convertLeoTree(v) appears to work, so leo.py appears to be largely compatible with scripting in LeoCB.
I spent almost an entire day messing with focus issues. There are two choices:

1. If tree.bodyKeepsFocus is true the body pane will always have focus unless we are editing a headline. The current headline is highlighted in grey to indicate the tree has no focus.

2. If tree.bodyKeepsFocus is false the body pane will not have focus if we select a headline by clicking on it. In that case the curren headline will be white text on a dark blue background. When focus shifts to the body pane, the current headline will be greyed.

After much experimentation, it seems that the first way is more convenient, though slightly less accurate as far as visual protocol goes. I think the convenience of having focus in the body pane (where it is useful) outweighs the small visual incongruity of having the current headline be grayed even when the tree canvas is selected.

Warning: Using frame.getFocus() is much less good than using the tree.active flag, because we can edit a headline with at most two clicks. That is not true when using frame.getFocus()

The bodyKeepsFocus logic affects all routines that alter the highlighting of headlines.
Changed OnCloseLeoEvent so it returns not veto.
TThe body handler didn't call setDirtyJoined. Changed setDirtyJoined so it returns a redraw flag.
Added virtual_event_name utility to generate < < name > >. Sigh.

The OnCut, OnCopy and OnPaste routines just call the body key event handler to do syntax coloring and set the various dirty/changed bits.

There is a small glitch: choosing cut/copy/paste "by hand" from the menu doesn't work when the focus is in an edit label in a headline. I'm not sure how to fix this and it is extremely minor anyway.
Added v param to tree.recolor so we don't assume that v is the current vnode.
I tried several options, with no luck: selectborderwidth, selectbackground, selectforground.
Added code to leo.py to set the window icon. Alas, the documentation is poor enough so that I don't know what the arguments to iconbitmap should be.
I tried a lot of experiments in fileCommands.getGlobals(), and it is still not possible to set the height and width of the top level window properly. It is still too large, and its size does not depend on the requrested width.

Note that the size of the window is set in createSplitter, by setting the size of a Frame object. Perhaps this is interfering with matters.

The present situation is poor, but tolerable.
Changed bd="2m" to bd=2. This makes a big difference in how the panel looks.
The syntax colorers can use tag_config to show tabs and spaces. This isn't perfect (it doesn't look good now) and does not work for newlines. It may be better than nothing, however.
I just forgot to add it to the list of @keywords.
These "bugs" may be appearing because these commands are always enabled in the menu. In any case, the fixes were straightforward.
The problem was that the body key handler was being scheduled before the new node was inserted and being called at idle time after the new node was inserted, so the body text disappeared. The fix was to disabling scheduling if control or alt keys are down.
All it took was the following:

font = tkFont.Font(family="Courier",size=9) tabw = font.measure(" ") self.body = body = Tk.Text(...font=font,tabs=tabw...)

This hard codes tabs as 4 spaces. Later I'll make it more general.
I am getting closer to getting the look of the splitter panes just right. Setting bg="white",relief="flat",bd=0 in createSplitter helps quite a bit.

The problem with the tree pane is clearly in the Canvas widget. If that widget is not packed the pane looks fine. There seems to be no way to eliminate the canvas's border, and no way to prevent the border from being overwritten by the Entry widget.

Maybe there is a way: the problem may be that Entry widgets are embedded in their own windows...
I will use leo.py from now on to develop leo.py. It is the only way to really get the bugs out.

Changed the code that computes the length of the edit box for headlines. We always use len(v.headString) now. This still does not quite work properly, but it is better.

Fixed a bug in the selection logic. The colors for the previous headline are changed only if the previous is not the same as the current. This can happen if we click in the icon or plus/minus icon of the current headline.

Added the v.iconx and v.icony ivars so the tree.OnHeadlineKey routine can just redraw the cursor in place if it needs to change. This is a workaround to a problem that I haven't been able to solve: namely, how to force the cursor to be visible again after the headline loses focus. This is more than a little kludgy, especially after creating the elegant endUpdate mechanisim. OTOH, it may point a way towards incremental redrawing of the screen.
Changed "darkblue" to "DarkBlue". The colors known to Tk are listed at http://tcl.activestate.com/man/tcl8.4/TkCmd/colors.htm

Changed print_stack() to Print statements in es() logic. This was more confusing than helpful.

Eliminated some accelerators (Linux only)

Added loaddir code to leo.py.
Added many terms to the Glossary.

Improved description of Leo2. Partially merged Leo1 and Leo2 documentation. It still isn't perfect.
Probably very few people could understand the description of Leo2.
I fixed the "last" serious bug in this version. What was happening was that the clone information in leoFileCommands.py and leoCommands.py was alternatively being written and not being written. The fix was to v.joinNodeTo: there was a bug that was fixed in the Borland version that had not been fixed in this version.
It turns out the updateCloneIndices routines in both LeoCB and leo.py are slightly buggy. Both can increment indices multiple times for the same tnode. In addition, LeoCB computes indices for parts of the tree beyond the actual tree, so that would increase the indices further, which is why the LeoCB version sometimes has a larger clone indices.

Neither of these "bugs" really makes a difference. In either case, nodes are joined properly.

To "minimize" the clone indices we would do the following:

index = 0 v = root # Zero all indices. while v and v != next: v.t.cloneIndex = 0 # Set clone index only if it has not already been set. while v and v != next: if v.t.cloneIndex == 0 and v.isCloned() and v.shouldBeClone(): index += 1 v.t.cloneIndex = index v = v.threadNext()

This might be just slightly slower than the "wasteful" way.
The directory compare routines in the Python lib do not seem to exist. The new routines compare only .py files.
The lineIndent var was not set properly in << Set lineIndent, linep and leading_ws >>, with the result that some sections contained extra leading whitespace in their body parts.

With this bug fixed, leo.py reads and writes all files correctly!
Fixed a bug that was causing the last newline of doc parts to be deleted. The problem was that string.rstrip() also strips newlines! With this bug fixed it appears that Leo.py can read and write @file nodes just like Leo2CB does.

Created the removeTrailingWs() in leoUtils that strips trailing blanks and tabs.
Rewrote the code in scanText and scanDoc that figures out where sentinels start.

The new nextLine and nextKind vars clarify what is going on. When a non-sentinel line is output, the trailing newline is part of a sentinel if and only if nextKind is some kind of sentinel.

I'm not convinced the code in scanDoc is correct...

This is a very useful comparison tool. It allows me to compare whitespace easily.
This puts up a model dialog. The hardest part was writing the code that centers the dialog on the screen. Sheesh.
I fixed many minor problems in preparation for releasing the first usable version of Leo.py.
These bugs were simple transcription errors; they do not occur in LeoCB.

v.childIndex() was always returning 0, so naturally v.createDependents() was not working well.

Added a call to v.unlink() in v.destroyDependents. The call to v.destroyTree() no longer does anything.
We no longer need setIcon!! Indeed, setIcon just calls c.redraw(), so as long as the callers enclose code in beginUpdate/endUpdate all will be well. This eliminates a _major_ mess, and will result in further simplifications: we no longer need the distinction between the setX and initX routines!
It turned out that tree.idle_body_key was the bottleneck, _not_ syntax coloring!

What was happening was this: tree.redraw was being called on _every_ keystroke, and that was causing a huge amount of memory allocation and deallocation. After a while everything ground to a halt.

The fix was very clever: I added a flag to c.endUpdate and tree.endUpdate, true by default. tree.redraw is called only if count==0 and flag==true. So calling endUpdate(false) prevents all redraws from happening in a range of code. This is a wonderful addition to the beginUpdate/endUpdate pattern.

Both tree.idle_body_key and v.setAncestorAtFileNodeDirty use this pattern. The result is that tree.redraw() is called only when the icon really and truly must change.
Amazing. I wrote some dummy code last night, read up on indices this morning, and got everything to work in a couple of hours. The result is very fast: no optimization is needed for Leo. I love Tkinter!

Added c.recolor and tree.recolor routines. This hooks should be called whenever the body text changes. Apparently there is no "OnTextChanged" event in Tk.
Implementing this was trivial: we use a app.clipboard ivar, a string. Using an internal clipboard means that we can't inadvertantly paste the XML representation of a string into body text.
The only major step left before releasing a functional version of Leo is to check the @file write code.
The Tk clipboard handling is confusing: there are routines for clearing and setting the clipboard, but no obvious way to get the contents of the clipboard. I could use an internal clipboard (part of the LeoApp class) for leo nodes, which would work pretty well. If I did that I might want other commands: cut, copy and paste between node clipboard and the text widgets...

There doesn't seem to be any Python support for the clipboard, which is weird.
This is a tedious task, no matter how it is done.
Simplified the code that sets the current vnode and initializes icons. Having tree.redraw update icons automatically helps a lot.

Added tree.initing hack to disable endEditLabel from marking the tree as changed. Actually, this probably isn't necessary, as top.title probably won't be redrawn until idle time anyway.
Here is how to get accelerators working:

fileMenu.add_command(label="New", accelerator="Ctrl+N", command=self.OnNew) self.top.bind("<Control-n>", self.OnNew)

Warning: using top.bind_all would not work well with multiple windows.

The accelerator="Ctrl+N" option merely sets the menu text(!). The bind_all method actually does the work. BTW, I had to change (self) to (self,event=None) in all the command handlers. When an accelerator is used the handler is called with two args. When the menu is used the handler is called with only one arg.

IDLE shows how to be a bit more clever with initialization. I probably should study the Idle code and be clever ;-) IDLE also use the configuration data base, and I probably should study that too.
There are now just two routines, v.computeIcon() and v.setIcon().

v.setIcon just does c.tree.redraw() (!)

c.tree.redraw() now does nothing if the update count > 0, so this is safe. Moreover, we can now enclose any code in c.beginUpdate()/c.endUpdate() without worry.

This is the way it is written in the Book.
The <Key> event is called _before_ the body text is updated, so it is not possible to properly update syntax coloring, icons or tnodes there. Instead, the <Key> event handler just registers an idle-time event to do the real work. Very simple!
The canvas widget appears to have an improper 1 or 2 pixel gray border. I have worked around this by limiting the size of the Tk.Entry widget: expanding it only when editing is enabled.
Following the IDLE code lead me astray. IDLE does not allow the headline text to be edited! At last some of the confusion is gone.
openWithFileName now shows the window before loading, so that reading messages become visible. To do this, we call app().root.update().

Made leoFrames bigger: this is done in createSplitter(!)
We close the initial open window if it has not been changed.

app.quit() now closes the hidden root window to close the app.

Close processing now calls the close event handler.

There are no bugs as in wxWindows! Everything is straightforward.
I have rewritten the code to draw the tree, and it seems to work reliably. However, expanding an entire tree is slow, and redrawing the tree is also slow is many nodes are visible. I'm not sure what is causing this...

Disabling drawNode speeds up the redraw greatly. Disabling only tags also speeds up the redraw greatly. Indeed, way too many tags appear to be created...

Still to do: I have not been able to create edit windows in the canvas properly...
I'm not sure whether Tkinter can read .bmp icons, so I created .gif icons, which work well.
Rewrote v.restoreOutlineFromDVnodes. It is now very simple because undo no longer creates dvnodes; vnodes now contain all tree information. So when we "delete" a tree we just unlink and unjoin it, and when we "restore a tree we just relink and rejoin it. It is truly amazing how simple the tree code now is.
It now takes about 3 seconds to read this file, rather than the 9+ seconds needed by wxPython! Apparently the wxPython tree class was very slow.

The _old_ bottleneck was as follows: Most of the time is spent in atFile scanning code .. c.clearAllVisited is very slow(!!) because it clears the entire tree. c.clearAllVisited takes about 1/3 time of the entire scanning phase (!!) .. Reading the file itself, is very fast, scanning in atFile::read is slow. .. es calls are fast.
A milestone. It is now possible to read a .leo file and to draw the tree at least partially. Most of the conversion to Tkinter has now been completed.

I removed vnode and commands getters that merely returned ivars. Also converted wxWindows calls to Tk calls. It is remarkable how easily the changes went.
Converted all Tk prototype code to Tkinter. It was straightforward for the most part. The Leo window works: scrollbars work and the text panes work. However, the tree pane is a text pane at present.

Copied menu code from Leo.py version 0.04. Somehow the old code was lost, I have no idea why. The copied code is wxWindows code, so it will have to be rewritten for Python. Still, it is much easier to use the properly converted Python code. Perhaps I deleted the code thinking that it would all have to be rewritten. If so, that was a minor mistake.
The use_tk global indicates whether Leo will use wxPython or Tkinter. The next step is to transliterate the Tk code that creates the Leo window into Tkinter.
After much experimentation, adding a -padx param to the log window pane seems like the only way to handle the width of the vertical scroll bar. Without this extra padding, the scroll bar obscures the left edge of the log window.
It simplifies and speeds up the code to use file.readline to process lines as a unit. This way I never have to scan to the end of a line. However, it turns out that scanning is not really the bottleneck. In addition, we no longer read entire derived files into memory at once.

BTW, it may be possible to use readline() in the getLeoFile logic as well. I have no idea whether this would help or not.

The problem is the _second_ half of atFile::read, that is, after the derived file has been completely scanned and all vnodes have been created! Apparently, something (maybe shouldBeClone?) involved with setting bits is horribly slow. I'm not sure why earlier testing didn't show this problem. I must have been mistaken somehow in my earlier conclusions.

This actually is very good news. It means that the raw file code is good enough as it is. Note: reading derived files will not slow down when I switch to Tk because we don't create vnodes unless there are file errors!

I have just found that a single call to c.clearAllVisited() in read() _substantially_ slows down the read! Amazing.

Create v.clearVisitedInTree() and v.clearAllVisitedInTree()
@nocolor @ignore

This is a _very_ easy class.

Ivars self.root: the root vnode self.update: update count tree::beginUpdate increments. tree::endUpdate decrements. Disable redraw if > 0 Redraw when falls to 0. self.selected: the selected vnode self.top the top vnode on the screen (needed?)

Tree methods know about vnodes(!) Greatly simplifies both tree and vnode classes. tree:endUpdate does nothing if self.update > 0 User code should not call tree::redraw directly. Most tree methods call redraw Insert methods create a vnode, set links and redraw. Move methods just set links and redraw Commands methods enclose complex operations in tree.beginUpdate/endUpdate, as they do now. tree::delete(v) just unlinks v's tree vnodes are not deleted until window goes away: unlimited undo! v.threadNext ivar makes deleting a list trivial.

vnodes

vnodes contain headString, expanded, firstChild, parent, back, next and threadNext ivars tree::getThreadNext() used to set v.threadNext no need for dvnodes: vnodes contain all undo info!

added use_tk constant in leoGlobals.
es now outputs a newline, which makes most uses of enl and ecnl superfluous. We can do this be string concatenation is so easy.
There was a transliteration error in computing the line to be output which mangled long lines.
This code has not been tested.
There was a bug in sentinelKind. skip_c_id won't handle @@, so @@ must be handled separately.
It is not possible to access global _variables_ using from leoGlobals import * because that creates copies of the data. The new way is simpler and more modular.

Revised how the prefs panel works. The new code just sets the prefs ivars. It is up to the caller to access the prefs panel as needed. Added tab_width preference and prefs.tab_width ivar. This should have been done long ago.
Fixed problems with at_xxx_ptr globals. The problem that from leoGlobals import * doesn't work, so I changed is_special_bits so it returns a dictionary containing name:value pairs rather than setting globals.

Fixed many problems in scanAllDirectives involving paths. Also rewrote the code in atFile::write that opens files.

At this point leo.py can read and write files except files containing @first.
.. initAllCloneBits had several errors. .. self.structureErrors wasn't being initialized in atFile::Read .. createNthChild must mark nodes as visited to suppress unvisited node logic later. .. Added log messages any time self.structureErrors is incremented in createNthChild

At this point leo.py can read a simple test file properly, except files containing @first.
It wasn't too hard to create a proper splitter window. After that, creating a Leo window was trivial.
I am beginning to think that globals should _never_ be used.

The @language bug appears to be a problem of not setting globals properly.
Added assert in tnode::setTnodeText that we are getting a string. This assert was not always true before I added a call to listToString in << copy new_out to child's body t't' in scanText.
The read code is slow. I'll have to see why.

3 seconds with read doing nothing (reads leoPy.leo) 5 seconds with read reading files but not scanning them. 9 seconds with read fully enabled.

Therefore:

3 sec to read and scan LeoPy.leo 2 sec for reading all derived files 4 sec to scan all derived files.

So scanning derived files is pretty slow.

BTW, LeoCB can do everything in about 2 sec.
There was the usual assortment of minor problems.

.. The write code was writing flags using `flag`, and this fails if false is None. .. Added code to make sure None is never passed to wxFileDialog as a file name. I wonder if this could have caused problems earlier. Once again, the C++ code is shown to be radically unsafe.

.. I think false should be defined as 0, not None. There are several advantages:

1. not 0 == not None, so various tests work as before. 2. `0` is very different from `None`, and this affects the file format.

I'll make this change. Hope it works ;-)
A major milestone: the read command works and so do most outline commands. It is now clear that reading a file is fast enough so that no further work is required!

The main problems today:

1. The code must use the test: id and id.IsOk(). Failure to make _both_ tests will cause the code to fail, possibly in an unbounded loop. This was ultimately the problem with the Move Down command: the threadNext code didn't make these test. I finally called wxGetThreadNext inside threadNext and that solved the problem.

2. I added the self.changing lockout in the LeoFrame OnTextUpdate handler so that tnodes would not be erroneously updated while switching tnodes. The new code seems simple and robust.
A case can be made for getters of the form:

def getX(): return x

In particular, they are a reaonable place to put asserts. However, such getters do not seem good style in Python because all ivars are public anyway. Moreover, getters like

def x(): return x

are invalid in Python, so there is a problem. The simplest approach seems to be to get rid of all such redundant getters, and that is what I have just done. Note that assert's aren't so important in wxPython, because nothing bad can happen anyway.
This was a very interesting experience, for the following reason.

1. The weakness of a non-compiled language is that problems don't show up until code is actually executed. 2. Python is very safe. The only real problem are unbounded loops. 3. In particular, the continue statement will _always_ loop in Python unless progress the loop variable is incremented. So care must be taken when translating C for loops into Python while loops. 4. Even without the ability to set breakpoints (which means that single-stepping is impossible in wxPython!) it wasn't too hard to trace with print statements. Amazing. 5. The biggest mess came with getters that return an ivar of the same name. I'll sleep on this, and I think the thing to do is get rid of the getter entirely: convert x.getY() x.Y. 6. Defining constants in a class is nice: they are accessed by class name. For example: vnode.dirtyBit.

Status: 1. The outline is being read in correctly. 2. The atFile::read logic hasn't been tested. 3. The body pane (and probably all the tnode text) is not being set properly. 4. It is possible that we might be able to get rid of join lists! They are used: a) To update the headline text b) For shouldBeClone. We could replace join lists by a reference count and temporary join lists. This could be really cool, and I'll have to think about this more...
Created leoFileCommands.py. We use com=self.mCommands to access the "real" commander for this class. It would be wrong to make fileCommands a subclass of Commands; we must have exactly one commander per frame so that mCurrentVnode, mRootVnode, etc. are updated properly.

In short, the ivars of fileCommands are mFrame, mCommands and any ivars used exclusively by fileCommands. This pattern will be used to create outlineCommands.py, findCommands.py, etc.
The info class was confusing. All the ivars from that class now become ivars in the fileCommands class. The mTnodes array is now a Python dict, so there is no need for the mNumberOfTnodes or mMaxTnodeIndex ivars! The new clode is _much_ clearer. A major victory.
Fixed several bugs in c2py.

Greatly simplified handlePossibleFunctionHeader; there is no need to compute a delta there: we munge the head, args and body separately, then replace the original all at once with the new lists. This is bullet proof.

Fixed some bugs in safeReplace and matchWord.

Fixed bugs in convertCStringToPython that caused skipDoc and skipCode to be called in the wrong places. That was really messing things up!

Tested the speed of tests by writing speedTest. This is so much fun!
I have been happily converting, with the help of c2py, Leo's C++ code to python. The new code is far, far easier to understand.
Fixed several bugs in c2py:

1. removeExcessWS was sometimes skipping past a newline, thereby removing leading whitespace in the following line. This made it look like the code for "if", "while" and "for" was bad.

2. the function scanning code had several bugs. Added new code to ignore "if", "while" and "for" code. Added new code to ignore # lines (preprocessor directives). Added the firstOpen variable to eliminate scanning later. This is pretty tricky code.

3. Added code to replace " . " by "." and "\t " by "\t".

4. Fixed skipPastWord so it handles underscores and digits. This helped a lot ;-)

5. @code was not always properly converted to @c.

I think I will leave c2py as is for now. It has the following rough edges:

1. I don't know how to break long scripts. 2. c2py doesn't handle : initializers in constructors well; they inhibit processing of the initializer. 3. c2py doesn't place self. in all the places it is needed. I'm not sure there is an elegant way to do this...
Worked on c2py. As documented in LeoCB.leo, had to add a __cmp__ routine in leo.py so that vnodes compare equal properly. Fixed some minor bugs in c2py.

We now have two separate routines called leo.py. The one used by LeoCB and LeoWX is the python expression evaluator. The leo.py in LeoPy is the top level of leo.py.

Added signed, unsigned and bool to the list of type names.
Documented the theory of operation of c2py. This is a remarkably simple program! Converting from string to list representation turns out to be a very good choice.
For the last two days I have been working on c2py.py. I am totally in love with Python. Everything is so much easier: Automatic debugging, no declarations, no types, no compilation. Great data structures. Even without single stepping the debugging is easy.

All major aspects of c2py are now complete.
It has been staring me right in the face. Idle.py supports tree views and syntax coloring! All I need do is adapt the Idle code for Leo. The only remaining piece of the puzzle is a good splitter window (and splitter events?)
All menu work is now complete, with stubs for many calls to Commands routines... The Python menu is functional. The Find Panel can be opened. The Find Panel can be opened.

Still to do: Icons for outline Nodes classes Commands classes: outline commands file commands, find commands, tangle/untangle commands
Leo does an auto-save only if something has actually been changed.
The config_encoding setting controls the encoding of non-ascii characters in leoConfig.txt.

The use_gnx setting in leoConfig.txt determines whether uses gnx's (cvs friendly) or ints. No version of Leo before 4.1 can read .leo files containing gnx. Only gnx's are immutable. Leo recomputes all non-gnx indices from scratch whenever writing a .leo file. It is possible to convert between 3.x and 4.1 file formats by changing the use_gnx setting.
Leo 4.1 beta 5 December 5, 2003

This is the fifth release of the reorganized 4.1 code base.

For the first time in months there are no serious bugs outstanding.

Warning: By default Leo 4.1 creates .leo files that can not be read by older versions of Leo.

Warning: Use the __wx_gui.py plugin with _extreme_ caution until further notice: bugs in this plugin could cause body text to be cleared improperly when changing nodes.

Beta 5: * Leo runs in batch mode when invoked with --script aScriptFile.py - Created "null gui" classes that do not use any gui code. * Leo supports Unicode characters (e.g. Chinese) in path and file names. * Fixed several long-standing bugs: - Fixed several minor problems with the Extract Section and Import commands. - Fixed problem that prevent the Find panel from working on the Mac. - Improved @url handling so that @url file:\\path\x.leo works properly. - Improved support for @lineending platform. - Leo honors outline/body pane ratio when opening .leo files. - Leo now ensures that newly-opened windows fit entirely on the screen. - Fixed a crasher when Undoing a Change All command when searching headlines. - Fixed other bugs resulting from the 4.1 reorg: - Fixed problem loading plugins on Linux. - Fixed crashers in the Extract Section and Extract commands. - Fixed a crasher when writing a file containing an undefined section. - Removed duplicate bindings in popup menus. - Made sure to show Leo's windows in wx gui plugin.

Beta 4: - Fixed several "reorg" bugs, some of which affected plugins. - Allow @directives and section references when executing scripts! - More than one section reference may now appear on the same line. - Use "utf-8" encoding for filenames under MacOS (Darwin) - Allow command or cmnd prefix for menu shortcuts under Darwin.

Beta 3: - Completed the reorg, making _many_ changes to the code base. - Fixed a LaTeX coloring bug

Beta 2: - Fixed a startup problem on Linux: the "mbcs" text encoding is not valid on Linux.

Beta 1: * Script-based find/change commands. - Hoist & DeHoist commands for viewing parts of an outline. - Check Outline command finds user clone mistakes. * New configuration settings: - use_gnx specifies whether .leo files use integers as indices: use_gnx = 0 makes .leo files compatible with previous versions of Leo. use_gnx = 1 makes .leo files cvs-friendly - config_encoding specifies the encoding of leoConfig.txt. * Several significant code-level changes: - The 4.1 code base has been reorganized to support gui plugins. - An example wx_gui plugin is partly functional. - Created a flexible framework for creating & running regression tests. - app.forceShutdown now works when called from plugins.
Leo 4.1 uses immutable gnx's (id:timestamp:n) by default to associate tnodes with vnodes in .leo files. This makes Leo as cvs friendly as possible. From now on .leo files will be checked in to cvs with the -ko (text/keywords off) option.
The file test.leo in the test directory contains real regression tests for syntax coloring. Regression testing scripts create regression tests dynamically from data in Leo's outline. Very cool, very easy, very general. In particular, regression tests may use temporary nodes in test.leo rather than creating separate Tk windows for testing.
The 4.1 code base has been reorganized to support gui's other than tkinter. Leo's src directory contains several new source files.

This reorg affects almost all plugins. Considerably more work and testing will be done before 4.1 final.

The __wx_gui.py plugin is uses the newly reoganized code. It is functional except for gui-dependent commands like Toggle-Split Direction. WARNING: treat the this plugin with _extreme_ caution until further notice. The possibility exists that bugs in the code could cause body text to be cleared improperly when changing nodes.
This involved fixing a bug in idle_body_key so that the oldText param is honored if present.
@nocolor

New in 4.1: Leo now supports scripts in Leo's Find/Change dialog.

Leo's find panel now contains the Script Search radio button and the Script Change checkbox. When the Script Search radio button is selected Leo treats the contents of the Search Text as a script to execute whenever any kind of Find command is executed. Similarly, when the Script Change checkbox is selected Leo treats the context of the Change Text as a script to execute whenever any kind of Change command is executed. See below for just how this works.

Script-based find-change is extremely powerful. In effect, Leo's Find/Change panel becomes a new platform for running scripts interactively. Leo now has all the find/change capability of pattern matching languages like Snobol and Icon, just by using the capabilities of the plain Python language. Moreover, Leo can deliver these capabilities interactively or not depending which buttons you push in Leo's Find/Change panel.

Script-based find/change frees the user from having to control script-based searches interactively. Rather than forcing find/change scripts to implement their own interactive controls, it is much easier to use what Leo already has.
Undoing Change All cleared the body pane or the headline pane.

The fix was simple: call setUndoParams instead of setUndoTypingParams in batchChange.
The node-only option wasn't working due to a bug in createFrame.

There still may be a problem with the interaction between Node-only,Change All and Undo.
The fix was to open files with 'rb' instead of 'r'. This probably should be done in more places.
This command checks for unused tnodeLists and checks the topology of all clones.
The Hoist command redraws the screen so presently selected tree becomes the only visible part of the outline. Leo prevents the you from moving nodes outside the hoisted outline. The De-hoist command restores the outline. Hoist commands may be nested.
The Check Outline command checks the outline for consistency and warns if it finds dubious clones.
The Hoist command redraws the screen so presently selected tree becomes the only visible part of the outline. Leo prevents the you from moving nodes outside the hoisted outline. The De-hoist command restores the outline. Hoist commands may be nested.
from leoGlobals import * import leoTest

u = leoTest.testUtils() nodes = 0 ; lines = 0 c = top() c.clearAllVisited() v = u.findNodeAnywhere(c,"Users Guide") after = v.nodeAfterTree() # trace(v,after) while v and v != after: if not v.t.isVisited(): v.t.setVisited() nodes += 1 lines += len(splitLines(v.bodyString())) v = v.threadNext()

pages = ((nodes * 10) + lines) / 50 print "nodes,lines,pages",nodes,lines,pages es("nodes,lines,pages",nodes,lines,pages)
Leo dedicates a Python dictionary called app.searchDict for communication between Leo and the search and change scripts. The search and change scripts may also use app.searchDict for communication between themselves. Leo sets app.searchDict["type"] to "find", "change", "findAll" or "changeAll" to indicate the kind of command being executed. Scripts may use all other entries in app.searchDict as they please.

Leo executes the find script once when you press the Find button (or the Change then Find button) in Leo's Find/Change dialog. Typically, the find script would traversing the tree and highlight the found text or otherwise indicate to the user that the find operation has suceeded. However, the script can do anything it pleases.

Leo executes the the find script repeatedly when you press the Find All button. Leo keeps executing the find script until app.searchDict["continue"] evaluates to false. Initially there is no entry for app.searchDict["continue"], so the find script must set app.searchDict["continue"] = true if it wants Leo to keep executing it.

Leo executes the change script once when you press the Change button. Typically, the change script would change the selected text. Usually the change script will compute the new value of body text and call v.setBodyStringOrPane(newText) to make that change permanent.

The change script also may handle undo, typically by calling c.frame.onBodyChanged(v,"Change",oldText=oldText). Leo does not handle undo automatically. Indeed, Leo makes no assumptions whatever about what the change script really does.

Leo executes the the change script repeatedly when you press the Change All button. Just as with the Find All command, Leo keeps executing the change script until app.searchDict["continue"] evaluates to false.

Most find and change scripts will ignore settings in the Find Panel like "whole word", "pattern match", and "reverse". However, these settings are available to the scripts via ivars such as c.whole_word_flag, etc. if desired.
@nocolor

Some ideas for using scripts:

Either the find and change scripts may use Python's re module. For example, the find script could set app.searchDict["m"] to the match object returned by re's match method. The change script would then compute the result, change the text and set the undo info as usual.

There is no reason to confine find/change scripts to operate on data only within a Leo outline. These scripts could as easily traverse your file system as the Leo outline. But notice: scripts could pull data from the file system into the outline so that you can see the effects of changes as the scripts operate. Again, this can all happen interactively if you like.
@language python @color

@ New in version 4.1: On startup, Leo looks for two arguments of the form --script scriptFile. If found, Leo enters batch mode.

In batch mode Leo does not show any windows. Leo assumes the scriptFile contains a Python script and executes the contents of that file using Leo's Execute Script command. By default, Leo sends all output to Leo's log pane to the console window. Scripts in the scriptFile may disable or enable this output by calling app.log.disable() or app.log.enable()

Scripts in the scriptFile may execute any of Leo's commands except the Edit Body and Edit Headline commands. Those commands require interaction with the user.

For example, the following batch script reads a Leo file and prints all the headlines in that file. @c

from leoGlobals import *

path = r"c:\prog\leoCVS\leo\test\test.leo"

app.log.disable() # disable reading messages while opening the file flag,newFrame = openWithFileName(path,None) app.log.enable() # re-enable the log. c = newFrame.c ; v = c.rootVnode() while v: print toEncodedString(v.headString(),"utf-8") v = v.threadNext()
Another 4.1 reorg bug.
This was due to a missing activate event.
Continuation lines caused the import command to terminate functions, methods and classes prematurely.
This was causing Leo not to load plugins properly on Linux. The problem was that the imp module does not handle Unicode file names properly.
This was a reorg bug.
Another bug caused by the 4.1 reorg.
- Leo now properly enables and disables the Extract Section menu item. - Leo no longer removes leading forward slashes in section names in headlines. - Corrected the documentation for the Extract Section command.
Another 4.1 reorg bug.
This bug has been around for a long time.
The new code attempts to use bitmap files only on Windows.
Leo makes sure that opened files or newly-created outlines fit entirely on the screen. The Cascade commands leaves the sizes of windows unchanged: it merely ensures that the top left corner of a window is visible on the screen.
@ A problem: the new window gets put behind the old. This is probably because Tk thinks that the mouse-down event should select then new window...
This is a very useful piece of software that has enabled me to use Literate Programming in production for the first time.

When I figured out I could do it in plain text and export to DocBook for automated typesetting, I was on my way. Because I only do business and utility software, I don't need the sophistication of LaTeX markup.

Writing the documentation and the code in the same outline at the same time improves the whole product. Being able to automatically export both with just two key presses (tangle and export-to-DocBook) is a real luxury. -- Michael Dawson
Plugins may create (or add to) v.tempAttributes (for vnodes) or t.tempAttributes (for tnodes). These will work just like the corresponding unknownAttributes dicts except that Leo will _not_ read or write these dictionaries when reading or writing files. Note: nothing needs to change in Leo to support this convention.
@nocolor

We wish to avoid the possibility that two plugins might unwittingly choose the same name for attributes in the unknownAttributes or tempAttributes dictionaries. The following convention eliminates that possibility.

Each plugin should create its own dictionary _inside_ any tempAttributes or unknownAttributes dictionaries that it intends to create or modify. Something like this:

@color

# Create the tempAttributes dict for this vnode. if not v.hasattr("tempAttributes"): v.tempAttributes = {}

# Create a subdictionary for the private use of my plugin. d = v.tempAttributes.get("myPluginName") if d is None: v.tempAttributes["myPluginName"] = d = {}

@nocolor

Now the plugin can set and get fields in d. Similar code would be used for the t.tempAttributes, v.unknownAttributes and t.unknownAttributes dictionaries.
ED, you continue to push the envelope. the amazing thing is that the footprint isn't doubling every few months like it would be in another designers hands. adding features by removing constraints, hot refactoring while adding unit tests. forget the book. I would pay to see the movie.
There is a trick that makes it very easy to develop Leo using Leo itself. The trick is to create a workflow that separates editing from testing.

Putting test code in LeoPy.leo would waste a lot of time. To run tests I would have to exit Leo and reload LeoPy. A much quicker way is to put all test code in a test.leo file. So to change and test code, just do the following:

1. Save LeoPy.leo but do NOT exit Leo.

2. Quit the copy of Leo running test.leo, then reload test.leo.

3. Run test scripts from test.leo.

That's all. Python will recompile any changed .py files in the new copy of Leo.

BTW, I use a batch file called t.bat that runs test.leo, so my "edit-reload-test" cycle is just:

1. Control-S (to save the .leo file) 2. t (to run load test.leo) 3. Control-E (to run the test script)

The benefits of the new workflow:

1. test.leo loads _much_ more quickly than LeoPy.leo does. This new approach can increase the speed of the edit-reload-test cycle by more than a factor of 10. Hitting Control-S, t, Control-E takes about 5 seconds.

2. LeoPy.leo continues to run with the _old_ code, so it is much easier to fix syntax errors or exceptions in the _new_ code: just fix the problem and save LeoPy.leo _without_ closing LeoPy.leo., then restart test.leo. Tests are run on new code; editing is done on old, stable code.

3. test.leo is the perfect place to develop test. I can create and organize those tests and when I am done, test.leo is a log of my work.

BTW, I have long wondered whether there was a way to recompile Leo's .py files without leaving Leo. Theoretically this is possible using reload, but in practice it doesn't work when objects contain state information. The new workflow neatly solves the problem while sidestepping the problems.
@nocolor

There is an importFiles script in LeoPy.leo. You can use that script as follows:

@color

import leoImport leoImport.importFiles(aDirectory, ".py")

@nocolor

This will import all .py files from aDirectory, which should be a full path to a particular directory. You could use ".c" to import all .c files, etc.
Leo 4.1 Release Candidate 3 December 19, 2003

This release fixes a serious bug in the logic that reads old-style derived files.

There are no known bugs in this version of Leo.

The highlights of this release:

- Leo runs in batch mode when invoked with --script aScriptFile.py - Leo supports Unicode characters (e.g. Chinese) in path and file names. - @directives and section references are now valid when executing scripts. - @ignored and orphan nodes now valid in @file-nosent trees. - Script-based find/change commands. - Check Outline command. - Hoist & DeHoist commands. - A new gui-agnostic architecture: useful for batch mode and unit tests. - Several new configuration settings. - Many new unit tests. - A host of bug fixes.
See previous release notes for more details:

- Fixed several minor problems with the Extract Section and Import commands. - Fixed problem that prevent the Find panel from working on the Mac. - Improved @url handling so that @url file:\\path\x.leo works properly. - Improved support for @lineending platform. - Leo honors outline/body pane ratio when opening .leo files. - Leo now ensures that newly-opened windows fit entirely on the screen. - Fixed a crasher when Undoing a Change All command when searching headlines. - Fixed other bugs resulting from the 4.1 reorg: - Fixed problem loading plugins on Linux. - Fixed crashers in the Extract Section and Extract commands. - Fixed a crasher when writing a file containing an undefined section. - Removed duplicate bindings in popup menus. - Made sure to show Leo's windows in wx gui plugin. - More than one section reference may now appear on the same line. - Use "utf-8" encoding for filenames under MacOS (Darwin) - Allow command or cmnd prefix for menu shortcuts under Darwin. - Fixed a LaTeX coloring bug
- The 4.1 code base has been reorganized to support gui plugins. - Created a flexible framework for creating & running regression tests. - app.forceShutdown now works when called from plugins. - Created app.unitTestDict for communication between tests and between tests and code.

- An example wx_gui plugin is partly functional.

Warning: Use the __wx_gui.py plugin with _extreme_ caution until further notice: bugs in this plugin could cause body text to be cleared improperly when changing nodes.
- use_gnx specifies whether .leo files use integers as indices:

use_gnx = 0 makes .leo files compatible with previous versions of Leo. use_gnx = 1 makes .leo files cvs-friendly

- config_encoding specifies the encoding of leoConfig.txt.
- Import Derived File command now uses a file dialog.

This is much more natural than using @file nodes.

- Leo now loads all plugins listed in pluginsManager.text.

It is no longer necessary to edit plugin files to enable or disable plugins.
New in 4.1: The plugins test suite creates a new convention: if a plugin has a function at the outer (module) level called unitTest, Leo will call that function when doing unit testing for plugins. So it would be good if writers of plugins would create such a unitTest funcion. The unitTest function should run some tests. All unitTest needs to do to indicate failure is throw an exception or cause an assert to fail. Leo's plugins test suite takes care of the rest.
- Corrected counts when replacing tabs/blanks. - Don't write tnodeList when executing scripts. - Fixed long-standing clone-drag bug. - Fixed inconsistency between how 4.x and 3.x handle @others. The code now works as it did in all 3.x versions, namely @others does not terminate doc parts. - Leo now properly sets clone marks when when cutting and pasting nodes. - Only double-quotes delimit strings in html files. This bug prevented Leo from untangling some valid html/xml files. - Leo now puts class docstrings in class nodes when importing .py files. - Fixed several reorg bugs.
<humanreadablestuff> << thanks,>> @doc for bringing Literate Programming to the unwashed masses, and helping me enjoy writing @code again </humanreadablestuff>
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.
Wow, wow, and wow. I just started using Leo about a month ago (I had looked at it about a year ago but didn't quite "get it" yet). Now I finally understand how to use clones and I realized that this is exactly how I want to organize my information. Multiple views on my data, fully interlinkable just like my thoughts.

The excellent ideas which are already implemented and the new ideas mentioned above make this a very exciting time for Leo. Thanks for a great tool, and I'm sure I'm not alone when I say I look forward to great things with Leo!
I *LIKE* it; After coding a larger-than-normal DOS (sometimes one doesn't have the luxury of choice:) batchfile with Leo (my first Leo project, auto-emailing based on filename substring... trivial but ugly in DOS, since no string/email functions), I was amazed at how different the experience was compared to flat-filing. It was almost Forth-like in the way that it was possible to work top-down or bottom-up at will (I believe this is the key to its strength, btw).

Also, I think that Leo has intrinsic qualities that make it work better than other things. For example: * the ideal comprehendible code/module size (at least for small-brains like mine) seems to be roughly what fits (with a largish font and default-sized window split) in the bottom pane; It is probably a good chunk size for non-programming tasks, also. * @ documentation is much cleaner than inline comments in a text file. * the fact that it is Python-based gives a feeling of freedom * The simplicity of text-only. For example,the MS-Word97 outliner is great, but how much time is wasted trying to un-screwup your text formatting. At first, I thought that I wanted rich-text, html-ish panes (like TreePad Plus, for example) for non-programming use of Leo; but on second thought, it would be probably be a distraction ~80% of the time.

Thanks for sharing! I hope to be able to contribute something (besides commentary), once I get more comfortable using Python.

Thanks again, Tarvin Rhodes
Im a newbie to Leo( a couple of weeks ) and I feel addicted to programming again...in fact it has resurrected a dead project of mine :) The Outline has proven most liberating in terms of testing ideas out...Thanks alot!
Leo 4.1 Release Candidate 4 February 3, 2004

This release fixes numerous bugs reported since 4.1 rc3 and makes several other minor improvements, including several nifty new plugins.

The highlights of Leo 4.1:

- Leo runs in batch mode when invoked with --script aScriptFile.py - Leo supports Unicode characters (e.g. Chinese) in path and file names. - @directives and section references are now valid when executing scripts. - @ignored and orphan nodes now valid in @file-nosent trees. - Script-based find/change commands. - Check Outline command. - Hoist & DeHoist commands. - A new gui-agnostic architecture: useful for batch mode and unit tests. - Several new configuration settings. - Many new unit tests. - Excellent new plugins. - A host of bug fixes.
See previous release notes for more details:

4.1 rc3:

- Corrected counts when replacing tabs/blanks. - Don't write tnodeList when executing scripts. - Fixed long-standing clone-drag bug. - Fixed inconsistency between how 4.x and 3.x handle @others. The code now works as it did in all 3.x versions, namely @others does not terminate doc parts. - Leo now properly sets clone marks when when cutting and pasting nodes. - Only double-quotes delimit strings in html files. This bug prevented Leo from untangling some valid html/xml files. - Leo now puts class docstrings in class nodes when importing .py files. - Fixed several reorg bugs.

4.1 rc2, rc1, betas, alphas:

- Fixed several minor problems with the Extract Section and Import commands. - Fixed problem that prevent the Find panel from working on the Mac. - Improved @url handling so that @url file:\\path\x.leo works properly. - Improved support for @lineending platform. - Leo honors outline/body pane ratio when opening .leo files. - Leo now ensures that newly-opened windows fit entirely on the screen. - Fixed a crasher when Undoing a Change All command when searching headlines. - Fixed other bugs resulting from the 4.1 reorg: - Fixed problem loading plugins on Linux. - Fixed crashers in the Extract Section and Extract commands. - Fixed a crasher when writing a file containing an undefined section. - Removed duplicate bindings in popup menus. - Made sure to show Leo's windows in wx gui plugin. - More than one section reference may now appear on the same line. - Use "utf-8" encoding for filenames under MacOS (Darwin) - Allow command or cmnd prefix for menu shortcuts under Darwin. - Fixed a LaTeX coloring bug
- The 4.1 code base has been reorganized to support gui plugins. - Created a flexible framework for creating & running regression tests. - app.forceShutdown now works when called from plugins. - Created app.unitTestDict for communication between tests and between tests and code.

- An example wx_gui plugin is partly functional.

Warning: Use the __wx_gui.py plugin with _extreme_ caution until further notice: bugs in this plugin could cause body text to be cleared improperly when changing nodes.
- use_gnx specifies whether .leo files use integers as indices:

use_gnx = 0 makes .leo files compatible with previous versions of Leo. use_gnx = 1 makes .leo files cvs-friendly

- config_encoding specifies the encoding of leoConfig.txt.
- Import Derived File command now uses a file dialog.

This is much more natural than using @file nodes.

- Leo now loads all plugins listed in pluginsManager.text.

It is no longer necessary to edit plugin files to enable or disable plugins.
This allows plugins to draw the status area in the icon area.
The old code was concatenating strings; the new code uses list.append.
- ESC closes the Find panel.

- Hitting the Return key is now equivalent to hitting the Find button.
Leo now reports the parent of the orphan node.
This bug happened when a move up command would cause a node to become the sibling of (a clone of) itself. In such cases, Leo simply selects the other cloned copy of the moved node.
The bug was causing Leo not to remember the comment delimiters in the @+leo sentinel. As a result, Leo was recognizing no sentinels and the command complained about missing @-leo sentinels.
- Leo now does a better job of checking and reporting read-only violations.

- Leo now clears the file-dirty mark only if the file was sucessfully saved.

Leo sets only ancetor @file nodes dirty (not nodes themselves) when altering outline structure, i.e., when moving, inserting, deleting, cloning and sorting nodes.
That is, when preprocessing the script (expanding section references, etc.) Leo now ensures that child nodes effectively end in a newline. This prevents unintended concatenation of lines.
A contained a space before a tab in the leading whitespace of the line.
- These commands did not properly count the number of changed nodes. - These commands now mark only changed nodes as dirty.
Fixed crashers in:

- Insert Headline Time/Date commands. - Change headline command. - Write Dirty/Missing @file Nodes commands. - Batch mode.
- Fixed a bug that caused cut and paste operations in headlines to revert when those commands were chosen from the Edit menu.

- Extended control-v bug fix to "darwin","freebsd4","freebsd5"

- Cut and paste are now always enabled when not editing the body pane. This isn't perfect, and is an improvement.
1. Fixed a bug that caused improper indentation of sections.

The bug happened in some cases when a section reference occured following non-whitespace.

2. Leo ensures that leading whitespace is not added when writing empty lines.

This _might_ fix the "Zope Sprint" bug: "blank" lines in the expansion of @others got leading whitespace.
Previously, if you hoist a node with no children and then insert a node (CTRL-I) then the child is created as a sibling of the hoisted node and consequently is not displayed.

Leo now creates the new node as the first child of the hoisted node.

Also, Leo disables the Clone command when the presently selected node is the root of a hoist.
This creates a status area at the bottom of the Leo Window. Plugins may write to this area using the status area convenience routines in leoTkinterFrame.py.
@nocolor

The Scripts node in LeoPy.leo contains a script called initScriptFind. This script makes it easy to set up script based searches as follows:

1. Put the following code in the root of a tree that will contain your script search:

@color # Initialize Leo's find panel using the named children of this node. from leoGlobals import * initScriptFind("Find script","Change script") # Second argument is optional. # Start searching at the top. top().selectVnode(c.rootVnode()) @nocolor

2. Put the search script in a child node called "Find script" (no quotes).

3. (Optional) Put the change script in a child node called "Change script"

4. Execute the code above. Leo does the following:

- Puts the body of the "Find script" into the find text of Leo's Find/Change dialog. - Puts the body of the "Change script" into the change text of Leo's Find/Change dialog. - Selects the Script Find radio button. - Selects the Script Change checkbox if the change script exists. - Selects the root of the entire outline.

Presto! Leo is ready for a script search.
This plugin uses Leo and docutils to mix program code and reST documentation.

The Python docutils package must be installed.http://docutils.sourceforge.net

If SilverCity is installed (http://silvercity.sourceforge.net) the plugin produces HTML output that is syntax colored.

The basic approach is that any Leo node that contains program code that needs documentation (in an ideal world it would be every node) has an associated child node whose headline is simply '@rst', that contains reST-style documentation. While this means that a typical program may have dozens (if not hundreds) of these @rst nodes, it produces documentation that I think is as close as I have ever seen to useful, easy-to-read documentation that is easy to create at the same time as an application is being coded.

There are several interelated aspects to this work:

- The reST plugin (renamed rst2) was hacked to manage Leo outlines that were a combination of normal code nodes and reSt nodes.

- The plugin now recognizes a @rst directive (that has no associated file name) in the headline of a node.

- When it encounters such a @rst Headline node, it writes the contents of the body of the @rst node's *parent* (which generally contains program code) to the reST output file (HTML) and then adds the reST content that is contained in the body of the @rst documentation node to the output HTML file,

- These @rst nodes are used in @file-nosent trees and the @rst nodes have an @ignore directive (plus @nocolor directive and @wrap directives) so that when the @file is updated the application code does not contain the reST-style documentation contained in the body of the @rst nodes. (This was the reason I had asked you in the Leo Forum to allow @file-nosent trees to work appropriately with @ignore nodes - Thank you for doing this right away.) There is no reason that the reST content couldn't be written to the @file as documentation other than I personally believe in a volume of documentation (including musings, failed ideas, questions, etc.) that is probably best kept separate from the actual *.py files.

- When you double-click on a plain rst node (no associated filename) Leo opens both the @rst documentation node and the parent node containing code in an external editor *simultaneously*. I happen to use Textpad and so the code is opened with python syntax coloring in one Textpad window and right next to it is the reST documentation. This makes it much much easier to code and do documentation at the same time. (Note that the plugin assumes that the python extension is '.tp' since that is what I use with TextPad.)

So why is this a big deal?

For the first time in my experience the documentation process:

1) is completely in sync with the process of code development.

2) allows the programmer to write as much documentation as he/she wants without the programming code becoming unreadable because of what would be considered excessive or intrusive documentation. (This applies whether documentation is intended for external consumption or just so a solo programmer can remember what he/she was thinking...)

My conclusion is that Leo + reST/docutils provides the perfect platform for optimal documentation. I have never completely understood what is meant by Literate Programming but in my mind this is practical Literate Programming.

Frankly, I think this is all a minor miracle but maybe I've lost perspective.

Steve Zatz
Adds a quick search to the toolbar in Leo.

A search box which behaves like a web site search is added, along with a "GO" button to do quick searches right from the main Leo window. All the current search options are retained except that "search body text" is explicitely set - mainly because this is by far the most common use case.

Pressing <CR> while editing the text automatically does a search. Repeated searches can be done by clicking the "GO" button.

The combo box also stores a list of previous searches, which can be selected to quickly repeat a search. When activating a previous search the original search mode is used.

Still to do:

- incremental search - reverse search - persist recent searches across Leo sessions - use INI file to set options for list size, etc.
Adds a node navigator to the toolbar. The navigator allows quick access to marked nodes. You can either go to the marked node or hoist the marked node.
Adds row/column indicators to the toolbar at the top of Leo Windows.
A minimal http plugin for LEO, based on AsyncHttpServer.py.

Use this plugin is as follows:

1. Start Leo with the plugin enabled. You will see a purple message that says something like: "http serving enabled on port 8080, version 0.9"

2. Start a web browser, and enter the following url: http://localhost:8080/ You will see a a "top" level page containing one link for every open .leo file. Start clicking :-)

You can use the browser's refresh button to update the top-level view in the browser after you have opened or closed files.
Automatically add nodes for common tasks. This can be used to generate boiler plate code to quickly build an outline.
This bug happened when executing the initScriptFind script. This bug could cause Leo not to be able to save files.
Here is a posting which might be helpful:

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

The "@first" directive is the key to output useable code in unsupported languages. For example, to use Leo with the Basic language:

->body of a "@file" header: -----------------------------Snippet @first $IFDEF LEOHEADER @delims ' @c $ENDIF -----------------------------End of snippet

So this would enable a basic compiler to "jump" over the "true" LEO-header-lines; wich gives you:

-----------------------Derived file begins with: $IFDEF LEOHEADER <-conditionnal compilation directive #@+leo-ver=4 <-these lines not compiled #@+node:@file QParser005.INC #@@first #@delims ' '@@c $ENDIF <-... Until here! <rest of derived code file ... > -----------------------end of snippet

This changes the comment symbol to " ' " apostrophe... making comments parseable by a BASIC (or other language.)
Adds a status line to the bottom of Leo windows.
The Change All command was looping when the find pattern was '\n'.
Leo 4.1 Final February 20, 2004

Leo 4.1 Final is the culmination of four months of work. No significant bugs have been reported since 4.1 rc4. Several people have contributed nifty plugins recently. See leoPlugins.leo for full details.

The highlights of Leo 4.1:

- Leo runs in batch mode when invoked with --script aScriptFile.py - Leo supports Unicode characters (e.g. Chinese) in path and file names. - @directives and section references are now valid when executing scripts. - @ignored and orphan nodes now valid in @file-nosent trees. - Script-based find/change commands. - Check Outline command. - Hoist & DeHoist commands. - A new gui-agnostic architecture: useful for batch mode and unit tests. - Several new configuration settings. - Many new unit tests. - Excellent new plugins. - A host of bug fixes.
Changed Undo/Redo messages:

- Insert Outline to Insert Node. - Delete Outline to Delete Node. - Clone to Clone Node.

This is a real code change: it affects the undo/redo logic.
Hoisting a node redraws the screen that node and its descendants becomes the only visible part of the outline. Leo prevents the you from moving nodes outside the hoisted outline. Dehoisting a node restores the outline.

Multiple hoists may be in effect: each dehoist undoes the effect of the immediately preceding hoist.
Clone bits could be improperly set in some cases. This bug would not have caused serious long-term problems.

Steps to reproduce:

1. Create a parent node (named 'cloned'). 2. Create two children nodes (one named 'dummy' and another named 'bottom') under the parent node 'cloned'. 3. Move the 'bottom' node below the 'dummy' node. 4. Clone node 'cloned'. 5. Copy node 'dummy'. 6. Paste node 'dummy' in between 'dummy' and 'bottom'. 7. After pasting you'll see that 'bottom' is now marked as a cloned node.
See leoPlugins.leo for full details.
Here is the bug report I submitted to Python:

The documentation for encoding lines at

C:\Python23\Doc\Python-Docs-2.3.1\whatsnew\section-encodings.html

states:

"Encodings are declared by including a specially formatted comment in the first or second line of the source file."

In fact, contrary to the implication, the Python 2.3 parser does not look for lines of the form:

# -*- coding: <encoding> -*-

For example, Python improperly scans the following line for an encoding

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

and reports that iso-8859-1. (note trailing dot) is an invalid encoding!

The workaround for my app is to precede this line with the following line:

# -*- coding: iso-8859-1 -*-

This makes Python 2.3 happy.

To make myself perfectly clear: Python has absolutely no right to complain about comment lines that do not have the form:

# -*- coding: <encoding> -*-
Leo works reliably only on MacOS X 10.3 (Panther).

For complete installation instructions see:

http://www.teti.ch/share/leo.php

Many thanks to Gaspard Bucher for these instructions.