back leo next

Chapter 8: Customizing Leo

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.

Specifying settings

Leo stores options in @settings trees, that is, 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. The key design goal of @settings trees was that Leo's user options must be infinitely flexible. That goal has been accomplished. Indeed, users can create arbitrarily complex user options with @settings trees. Leo settings outlines are, in fact, much more flexible and powerful than any scheme based on flat text.

The myLeoSettings.leo file is a way of ensuring that your customized settings are not altered when updating Leo from cvs or while installing a new version of Leo. The myLeoSettings.leo acts much like Python's site-customize.py file. The myLeoSettings.leo file will never be part of any Leo distribution, and it will never exist in Leo's cvs repository. This solution is much better than trying to update leoSettings.leo with scripts.

The Settings command opens the file leoSettings.leo.

Leo stores options in @settings trees, that is, parts of Leo outlines whose root node has the headline @settings. When opening a .leo file, Leo looks for @settings trees in the following places:

  • The file leoSettings.leo in the leo/config directory.
  • The file leoSettings.leo in the user's home directory.
  • The file myLeoSettings.leo in the leo/config directory.
  • The file myLeoSettings.leo in the user's home directory.
  • The file being loaded.

Settings that appear later in the above list override settings found earlier. For example, any setting specified in an @settings tree in the file being loaded overrides any setting seen in any leoSettings.leo file.

The following sections describe the kinds of nodes in @settings trees.

Organizer nodes

Organizer nodes have headlines that do no start with @. Organizer nodes may be inserted freely without changing the meaning of an @setting tree.

@ignore nodes

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.

Simple settings nodes

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

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

The actual settings are specified in the body text. At present, there are seven kinds of complex settings nodes:

  • @buttons

    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.

  • @commands

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

  • @data

    The body text contains a list of strings, one per line. Lines starting with '#' are ignored.

  • @enabled-plugins

    The body text of the @enabled plugins node contains a list of enabled plugins. This body text is treated exactly as the contents of the pluginsManager.txt file. Notes:

    1. Leo attempts to load all plugins every time an @enabled-plugins node is seen. If the plugin has already been loaded, Leo silently ignores the request to re-enable the plugin. Leo never attempts to disable a plugin while processing enabled plugin strings. Thus, plugins enabled in an @enabled-plugins node in leoSettings.leo will be enabled regardless of the contents of any other @enabled-plugins node.
    2. Leo will read the pluginsManager.txt file only if no @enabled-plugins node is found. The pluginsManager.txt file will no longer be part of the Leo distribution. Instead, the default plugins will be enabled in an @enabled-plugins node in leoSettings.leo.
    3. g.app.gui.getEnabledPlugins contains the last value last processed @enabled-plugins node, or the contents of the first pluginsManager.txt file encountered.
  • @font

    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.

  • @menus

    Leo creates its menus from the @menu and @item nodes in the @menus tree. Within @menus trees, @menu nodes create menus and @item nodes create menu items.

    The menu name always follows @menu. If the menu name is 'Plugins', Leo will create the Plugins menu and populate the menu by calling the 'create-optional-menus' hook. This creates the Plugins menu as usual. Nested @menu nodes define submenus.

    The command name follows @item. If the body text of an @item node exists, this body text is the menu name. Otherwise, the menu name is the command name. However, if the command name starts with a '*', hyphens are removed from the menu name. Menu names and command names may contain a single ampersand (&). If present, the following character is underlined in the name. If the command name in an @item node is just a hyphen (-), the item represents a menu separator.

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

  • @recentfiles

    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.

  • @shortcuts

    The body text contains a list of shortcut specifiers.

Input modes

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:

  • You can exit any mode using the keyboard-quit (Control-g) command. This is the only binding that is automatically created in each mode. All other bindings must be specified in the @mode node. In particular, the bindings specified in @shortcuts nodes are not in effect in mode (again, except for the keyboard-quit binding).
  • Leo supports something akin to tab completion within modes: if you type a key that isn't bound in a mode a 'Mode' tab will appear in the log pane. This tab shows all the keys that you can type and the commands to which they are bound. The mode-help command does the same thing.
  • @shortcuts nodes specify the bindings for what might be called the 'top-level' mode. These are the bindings in effect when no internal state is present, for example, just after executing the keyboard-quit command.
  • The top_level_unbound_key_action setting determines what happens to unbound keys in the top-level mode. Leo ignores unbound keys in all other modes. The possibilities are 'insert', 'replace' and 'ignore'.
  • The set-insert-mode, set-overwrite-mode and set-ignore-mode commands alter what happens to unbound keys in the top-level mode.

With all these options it should be possible to emulate the keyboard behavior of any other editor.

Adding extensible attributes to nodes and .leo files

Leo's .leo file format is extensible. The basis for extending .leo files are the t.unknownAttributes and v.unknownAttributes ivars of tnodes and vnodes, or uA's for short. Leo translates between uA's and xml attributes in the corresponding <v> and <t> elements in .leo files. Plugins may also use v.tempAttributes or t.tempAttributes ivars to hold temporary information that will not be written to the .leo file.

Collectively, these four kinds of 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. For example, a plugin named 'xyzzy' would set t.unknownAttributes as follows:

# Create the uA if necessary.
if not hasattr(p.v.t,'unknownAttributes'):
    p.v.t.unknownAttributes = {}

# Get the inner dictionary for the 'xyzzy' plugin, creating it if necessary.
d = p.v.t.unknownAttributes.get('xyzzy',{})

# Set some values. These values must be picklable.
d ['duration'] = 5
d ['notes'] = "This is a note."

# Update the uA.
p.v.t.unknownAttributes ['xyzzy'] = d

if hasattr(p.v.t,"unknownAttributes"):
    d = p.v.t.unknownAttributes.get("xyzzy",{})
    g.es(d['duration'])
    g.es(d['notes'])

Plugins would use similar code to create v.unknownAttributes, t.tempAttributes, and v.tempAttributes ivars.

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.

Important: Plugins must not use v.unknownAttributes inside @thin trees. Indeed Leo uses hidden machinery to write t.unknownAttributes. Leo does not write t.unknownAttributes to thin derived files. Instead Leo writes a representation of all t.unknownAttributes contained in the @thin tree to a special xml attribute called descendentTnodeUnknownAttributes in the <v> element corresponding to the @thin node. Yes, this is complicated, but it works. Leo can not write v.unknownAttributes in @thin trees because only tnodes have gnx's in thin derived files. In effect, vnodes are anonymous.

Plugins that must associate attributes with vnodes should support only @file trees. A completely different alternative would be for the plugin to extend how Leo reads and writes <v> elements in .leo files, but that would be much more complicated than using t.unknownAttributes

Here are the details about how Leo associates uA's with <v> and <t> elements in .leo files:

  • Native xml attributes are the attributes of <v> and <t> elements that are known (treated specially) by Leo's read/write code. The only native attribute of <t> elements is tx. The native attributes of <v> elements are a, t, vtag, tnodeList, marks, expanded and descendentTnodeUnknownAttributes. All other attributes of <v> and <t> elements are foreign xml attributes.
  • When reading a .leo file, Leo will create t.unknownAttributes or v.unknownAttributes ivars for any tnode or vnode whose corresponding <v> or <t> element contains a foreign xml attribute.
  • When writing a file, Leo will write foreign xml attributes in <v> or <t> elements if the corresponding vnode or tnode contains an unknownAttributes ivar.
  • Leo performs the usual xml escapes on these strings when reading or writing the unknownAttributes ivars.

Specifying Tk options using .leo_xresources

Leo looks for a file called .leo_xresources in the users home directory. If found, Leo will pass that file to Tk's option_readfile method for the top widget. This allows users to set Tk options.

Translating Leo's menus and messages

It is easy to translate Leo's menu strings: simply create an @menus tree in leoSettings.leo or myLeoSettings.leo that contains the translated menu names.

New in Leo 4.4.8: Leo now contains support for translating messages sent to Leo's log:

  • Rather than using an '_' function to denote strings to be translated, Leo's g.es and g.es_print functions translate "odd" (first, third, fifth) arguments, leaving "even" arguments untranslated. Keyword arguments, color, newline, etc. are never translated.
  • All calls to g.es and g.es_print in Leo's core follow this convention.
  • g.translateString does the actual translation using Python's gettext module.
  • You can use the script in the node "@button print g.es stats" in scripts.leo to create catalogs of all scripts that need to be translated. Such catalogs are used by Python's gettext module. (This script was also used to check that the proper arguments to g.es and g.es_print were translated.)

back leo next