This chapter discusses how to customize Leo using the plugins and other means. See Specifying settings for a description of how to change Leo’s settings.
Contents
Leo stores options in @settings trees, outlines whose headline is @settings. When opening a .leo file, Leo looks for @settings trees not only in the outline being opened but also in various leoSettings.leo files. This scheme allows for the following kinds of settings:
There are four kinds of settings files:
The following sections describe the kinds of nodes in @settings trees.
Settings files can be found in the following directories:
In addition, Leo’s -c command-line option can specify any .leo file anywhere.
When reading a .leo file, Leo looks for settings in default settings files first, then settings in personal settings files, and finally settings in local settings files. The exact search order is:
Settings that appear later in this list override settings that appear earlier in this list. This happens on a setting-by-setting basis, not on a file-by-file basis. In other words, each individual setting overrides only the corresponding setting in previously-read files. Reading a setting file does not reset all previous settings. Note that the same file might appear several times in the search list. Leo detects such duplicate file names and only loads each settings file once. Leo remembers all the settings in settings files and does not reread those settings when reading another .leo file.
Caution: This search order offers almost too much flexibility. This can be confusing, even for power users. It’s important to choose the “simplest configuration scheme that could possibly work”. Something like:
Important: it is good style to limit settings placed in myLeoSettings.leo to those settings that differ from default settings.
You should use special care when placing default or personal settings files in local directories, that is, directories other than homeDir, configDir or machineDir. In particular, the value of localDir can change when Leo reads additional files. This can result in Leo finding new default and personal settings files. The values of these newly-read settings files will, as always, override any previously-read settings.
Let us say that a setting is volatile if it is different from a default setting. Let us say that settings file A.leo covers settings file if B.leo if all volatile settings in B.leo occur in A.leo. With these definitions, the safe rule for placing settings files in local directories is:
Settings files in local directories should
cover all other settings files.
Following this rule will ensure that the per-directory defaults specified in the local settings file will take precedence over all previously-read default and personal settings files. Ignore this principle at your peril.
Organizer nodes have headlines that do no start with @. Organizer nodes may be inserted freely without changing the meaning of an @setting tree.
Leo ignores any subtree of an @settings tree whose headline starts with @ignore.
You can use several other kinds of nodes to cause Leo to ignore parts of an @settings tree:
@if expression
A node whose headline starts with @if expression acts like an organizer node if the expression evaluates to True, otherwise acts like an @ignore node. If the expression is empty the body text should contain a script that will be evaluated (in an empty context).
@ifplatform platform-name
Same as @if sys.platform == “platform-name”: except that it isn’t necessary to import sys.
@ifhostname hostA,!hostB
Evaluates to True if and only if: h=g.computeMachineName(); h==hostA and h!=hostB. The ”!” version allows matching to every machine name except the given one to allow differing settings on only a few machines.
Simple settings nodes have headlines of the form:
@<type> name = val
set the value of name to val, with the indicated type.
<type> may be one of the following:
<type> | Valid values |
@bool | True, False, 0, 1 |
@color | A Tk color name or value, such as ‘red’ or ‘xf2fddff’ (without the quotes) |
@directory | A path to a directory |
@float | A floating point number of the form nn.ff. |
@int | An integer |
@ints[list] | An integer (must be one of the ints in the list). Example: @ints meaningOfLife[0,42,666]=42 |
@keys[name] | Gives a name to a set of bindings for the Check Bindings script in leoSettings.leo. |
@path | A path to a directory or file |
@ratio | A floating point number between 0.0 and 1.0, inclusive. |
@string | A string |
@strings[list] | A string (must be one of the strings in the list). Example: @strings tk_relief[‘flat’,’groove’,’raised’]=’groove’ |
Note: For a list of Tk color specifiers see:
Important: you can use the show-colors minibuffer command to guide you in making these settings.
Complex settings nodes have headlines of the form:
@<type> description
The type may be one of the following:
<type> | Valid values |
@buttons | Child @button nodes create global buttons |
@commands | Child @command nodes create global buttons |
@data | Body text contains a list of strings, one per line. |
@enabled-plugins | Body text contains a list of enabled plugins |
@font | Body text contains a font description |
@menus | Child @menu and @item nodes create menus and menu items. |
@menuat | Child @menu and @item nodes modify menu tree create by @menus. |
@mode [name] | Body text contains a list of shortcut specifiers. |
@recentfiles | Body text contains a list of file paths. |
@shortcuts | Body text contains a list of shortcut specifies. |
Complex nodes specify settings in their body text. See the following sections for details.
An @buttons tree in a settings file defines global buttons that are created in the icon area of all .leo files. All @button nodes in the @commands tree create global buttons. All @button nodes outside the commands tree create buttons local to the settings file.
An @commands tree in a settings file defines global commands. All @command nodes in the @commands tree create global commands. All @command nodes outside the commands tree create commands local to the settings file.
The body text contains a list of strings, one per line. Lines starting with ‘#’ are ignored.
The body text of the @enabled plugins node contains a list of enabled plugins, one per line. Comment lines starting with ‘#’ are ignored. Leo loads plugins in the order they appear. Important: Leo handles @enabled-plugins nodes a differently from other kinds of settings. To avoid confusion, please read the following carefully.
As always, Leo looks for @enabled-plugins nodes in settings files in the order specified by Search order for settings files. Leo will enable all plugins found in the @enabled-plugins node it finds last in the search order. Leo does not enable plugins found in any other @enabled-plugins node. In particular, you can not specify a list of default plugins by placing that list in a settings file that appears early in the search list. Instead, the last @enabled-plugins node found in the search list specifies all and only the plugins that will be enabled.
Let us distinguish two different situations. First, what Leo does when loading a file, say x.leo. Second, what Leo does when loading a second file, say y.leo, from x.leo. When loading the first .leo file, Leo enables plugins from the @enabled-plugins node it finds last in the search order. But after plugins have already been loaded and enabled, there is no way to disable previously loaded-and-enabled plugins. But local settings files can enable additional plugins.
To avoid confusion, I highly recommend following another kind of safe rule. We say that an @enabled-plugin node in file A.leo covers an @enabled-plugin node in file B.leo if all plugins specified in B’s @enabled-plugin node appear A’s @enabled-plugin node. The safe rule for plugins is:
@enabled-plugin nodes in settings files in local directories
should cover @enabled-plugins nodes in all other settings files.
The body text contains a list of settings for a font. For example:
body_text_font_family = Courier New
body_text_font_size = None
body_text_font_slant = None
body_text_font_weight = None
Important: you can use the show-fonts minibuffer command to guide you in making these settings.
The form of this node is:
@mode *<mode name>*
The body text contains a list of shortcut specifiers. @mode nodes work just like @shortcuts nodes, but in addition they have the side effect of creating the enter-<mode name>-mode command.
The body text contains a list of paths of recently opened files, one path per line. Leo writes the list of recent files to .leoRecentFiles.txt in Leo’s config directory, again one file per line.
The body text contains a list of shortcut specifiers.
Leo now allows you to specify input modes. You enter mode x with the enter-x-mode command. The purpose of a mode is to create different bindings for keys within a mode. Often plain keys are useful in input modes.
You can specify modes with @mode nodes in leoSettings.leo. @mode nodes work just like @shortcuts nodes, but in addition they have the side effect of creating the enter-<mode name>-mode command.
Notes:
With all these options it should be possible to emulate the keyboard behavior of any other editor.
Leo’s .leo file format is extensible. The basis for extending .leo files are the v.unknownAttributes ivars of vnodes, uA’s for short. Leo translates between uA’s and xml attributes in the corresponding <v> elements in .leo files. Plugins may also use v.tempAttributes ivars to hold temporary information that will not be written to the .leo file. These two ivars are called attribute ivars.
Attribute ivars must be Python dictionaries, whose keys are names of plugins and whose values are other dictionaries, called inner dictionaries, for exclusive use of each plugin.
The v.u Python property allows plugins to get and set v.unknownAttributes easily:
d = v.u # gets uA (the outer dict) for v
v.u = d # sets uA (the outer dict) for v
For example:
plugin_name = 'xyzzy'
d = v.u # Get the outer dict.
inner_d = d.get(plugin_name,{}) # Get the inner dict.
inner_d ['duration']= 5
inner_d ['notes'] "This is a note."
d [plugin_name] = inner_d
v.u = d
No corresponding Python properties exist for v.tempAttributes, so the corresponding example would be:
plugin_name = 'xyzzy'
# Get the outer dict.
if hasattr(p.v,'tempAttributes'): d = p.v.tempAttributes
else: d = {}
inner_d = d.get(plugin_name,{}) # Get the inner dict.
inner_d ['duration'] = 5
inner_d ['notes'] = "This is a note."
d [plugin_name] = inner_d
p.v.tempAttributes = d
Important: All members of inner dictionaries should be picklable: Leo uses Python’s Pickle module to encode all values in these dictionaries. Leo will discard any attributes that can not be pickled. This should not be a major problem to plugins. For example, instead of putting a tnode into these dictionaries, a plugin could put the tnode’s gnx (a string) in the dictionary.
Note: Leo does not pickle members of inner dictionaries whose name (key) starts with str_. The values of such members should be a Python string. This convention allows strings to appear in .leo files in a more readable format.
Here is how Leo associates uA’s with <v> elements in .leo files:
This section describes the process of creating an importer for a new language. There are a set of “importers” in leoImport.py, all based on the baseScannerClass class. You can define your own importer by creating a subclass. This shouldn’t be too difficult: baseScannerClass is supposed to do almost all the work. With luck, your subclass might be very simple, as with class cScanner.
Important As I write this, I realize that I remember very little about the code, but I do remember its general organization and the process of creating a new importer. The following should be all you need to write any importer.
This base class has three main parts:
You should never have to change the code generators or the checking code. Confine your attention to the parser.
The parser thinks it is looking for classes, and within classes, method definitions. Your job is to tell the parser how to do this. Let’s look at part of the ctor for baseScannerClass for clues:
# May be overridden in subclasses.
self.anonymousClasses = [] # For Delphi Pascal interfaces.
self.blockCommentDelim1 = None
self.blockCommentDelim2 = None
self.blockCommentDelim1_2 = None
self.blockCommentDelim2_2 = None
self.blockDelim1 = '{'
self.blockDelim2 = '}'
self.blockDelim2Cruft = [] # Stuff that can follow .blockDelim2.
self.classTags = ['class',] # tags that start a tag.
self.functionTags = []
self.hasClasses = True
self.hasFunctions = True
self.lineCommentDelim = None
self.lineCommentDelim2 = None
self.outerBlockDelim1 = None
self.outerBlockDelim2 = None
self.outerBlockEndsDecls = True
self.sigHeadExtraTokens = [] # Extra tokens valid in head of signature.
self.sigFailTokens = []
# A list of strings that abort a signature when seen in a tail.
# For example, ';' and '=' in C.
self.strict = False # True if leading whitespace is very significant.
Naturally, this looks like gibberish at first. I do not remember what all these things do in detail, although obviously the names mean something. What I do remember is that these ivars control the operation of the startsFunction and startsClass methods and their helpers (especially startsHelper) and the methods that call them, scan and scanHelper. Most of these methods have a trace var that will enable tracing during importing.
So the strategy is simple: study startsHelper in detail, set the ivars above to make startsHelper do what you want, and trace until things work as you want.
There is one more detail. Sometimes the ivars above are not sufficient to get the job done. In that case, subclasses will override various methods of the parser, but not the code generator. If indentation is important, you will want to look at the Python importer. Notice that it overrides skipCodeBlock, called by startsHelper.
That’s about it. It would be pointless to give you more details, because those details would lead you away from the process you need to follow. Having said that, feel free to ask further questions. I’ll be glad to answer them.