pmacs3/MODES

122 lines
5.6 KiB
Plaintext

This document is designed to be a quick (and incomplete) description of how
modes work, how you might create one yourself, etc.
1. What are modes?
Pmacs uses modes to determine what which keys should apply which actions, how
the buffer should be highlighted, how the buffer should be indented, and any
other per-buffer configuration. The default mode ("Fundamental") provides the
base functionality which all other modes inherit. The idea of modes is taken
from Emacs (although the distinction between major and minor modes does not
exist in Pmacs--there are no minor modes).
2. Where do they come from?
Modes are loaded and installed by application.py, or by $HOME/.pmc/conf, which
is sourced by application.py. To install a mode, import its class from the
package in which it resides, and call install() on the class, passing the
application as the only argument. Example for the "toy" mode:
import toy
toy.install(self)
This code could be run in the application's constructor (as is the case with the
built-in modes) or in $HOME/.pmc/conf (which is the easiest way for end-users to
customize the program).
3. How do they work?
The one thing every mode has in common is that they map key bindings to actions
to be taken. They do this via self.bindings, a dictionary mapping action names
(i.e. 'page-down') to a tuple of key bindings (i.e. ('C-v', 'PG_DN',)).
Modes subclass mode.Fundamental, and they call mode.Fundamental.__init__ to run
the standard mode initialization (including building the default bindings
dictionary); they can later modify or overwrite this dictionary if they choose.
There are at least 3 optional behaviors modes can make use of:
1. Syntax highlighting
2. Indentation level detection
3. Tag (parenthesis, brace, bracket, etc.) matching
Not all modes can (or should) make use of these features: they are primarily
useful in modes having to do with programming languages, or other structured
documents.
4. Syntax highlighting
Syntax highlighting uses a hybrid lexing/parsing process to break each line of
the buffer down into one or more lexical tokens; these tokens are primarily
used to highlight parts of the buffer different colors. The colors to use are
defined by application.colors, a dictionary mapping token-names to a tuple
consisting of at least a foreground color and a background color. Modes can
define a self.colors dictionary which will be added to the application; however,
modes are not permitted to override the global defaults in this way (the user
can override them via ~/.pmc/conf in whatever way is desired).
Modes are encouraged to use "generic" token names when appropriate; modes can
also "namespace" their tokens to allow for mode-specific customization.
Explaining how to write a Grammar is outside the scope of this document; see
lex.py, mode.py and mode/*.py for examples. Some important points to note:
* Regexes are applied to only one line of the document at a time.
* All regexes must match at least one character (the newline counts).
* All tokens must consist of at least one character.
* A rule that matches must generate one or more tokens.
* Any input not matched by a rule will end up in a "null" token.
* Tokens can't "look" for other tokens (but they can use 0-width assertions
to test for data on the current line).
* Regions of text which begin and end with recognizable tokens can be
lexed using a different sub-grammar using RegionRule, etc. This nesting
can be arbitrarily deep.
* sub-grammars can be dynamically specified in modes that provide one or
more OverridePatternRule objects.
5. Indentation level detection
Indentation level detection hooks into the basic 'insert-tab' action; rather
than inserting 4 spaces (which is the default), it will instead determine the
correct "tab depth" for this line of the buffer, and insert/remove spaces from
the beginning of the line in order to reach the correct number.
[NOTE: 4 spaces is the norm; some modes default to 2. The Mode.tabwidth variable
determines this, and can be customized either during startup, or via the methods
set-tab-width (to set the width for a single buffer) and set-mode-tab-width (to
set the default width for all buffers using the given mode).]
To implement this, you must create a Tabber class and assign it to self.tabber
in the mode. At a minimum, a tabber must support the following methods:
* __init__(self, mode)
* get_level(self, y)
* region_added(self, p, lines)
* region_removed(self, p1, p2)
Tabber classes can often be tricky to implement correctly. tab.Tabber provides
a lot of base functionality that is probably useful; also, you may want to try
looking at tab.StackTabber, which provides even more base support.
6. Tag matching
Tag matching allows a closing tag (such as ")") to "show" the corresponding
opening tag (such as an earlier "(") to help orient the user. Currently, tags
are assumed to be single characters, although in the future it's easy to imagine
multi-character tags being useful. Currently they are not supported.
This support is very easy to add, assuming that the mode has a grammar. In most
cases, here is how it works:
a. In the mode, create 4 tuples:
* opentokens: tuple of lexical tokens which can be opentags
* opentags: dictionary mapping opentag strings to closetag strings
* closetokens: tuple of lexical tokens which can be closetags
* closetags: dictionary mapping closetag strings to opentag strings
b. Also in the mode, create or instantiate actions who subclass
method.CloseTag (e.g. method.CloseParen) and assign them the appropriate
binding (e.g. '(').
c. Enjoy!