Defining polymodes always starts with defining (or reusing) a hostmode and one or more innermodes.
Hosts Modes
Here is how the markdown hostmode is defined:
(define-hostmode poly-markdown-hostmode
:mode 'markdown-mode)
define-hostmode
is a macro which, in this case, simply defines an object
poly-markdown-hostmode
of class pm-host-chunkmode
. Keyword arguments are
slots, of which the most important being :mode
naming the emacs' major-mode
which should be installed in host chunks.
The optional second positional argument to define-hostmode
is the parent
hostmode. When provided the entire configuration of the parent is inherited by
the child. A wide range of hostmodes is already defined in the
polymode-base.el
file. Please re-use those in your own polymodes.
Inner Modes
Polymode defines two built-in types of innermodes - pm-inner-chunkmode
which
allows for one pre-specified in advance mode, and pm-inner-auto-chunkmode
which detects the major mode of the body span on-the-fly.
Here is an example of the YAML metadata innermode for markdown
(define-innermode poly-markdown-yaml-metadata-innermode
:mode 'yaml-mode
:head-matcher "\`[ \t\n]*---\n"
:tail-matcher "^---\n"
:head-mode 'host
:tail-mode 'host)
and a fenced-code auto innermode
(define-auto-innermode poly-markdown-fenced-code-innermode
:head-matcher (cons "^[ \t]*\\(```{?[[:alpha:]].*\n\\)" 1)
:tail-matcher (cons "^[ \t]*\\(```\\)[ \t]*$" 1)
:mode-matcher (cons "```[ \t]*{?\\(?:lang *= *\\)?\\([^ \t\n;=,}]+\\)" 1)
:head-mode 'host
:tail-mode 'host)
In both of the examples :head-matcher
and :tail-matcher
are regular
expressions patterns used to search for heads and tails of inner code
chunks. The :mode-matcher
tells polymode how to retrieve the major mode from
the head of the chunk. Each of the three marchers can be a regexp, a cons of the
form (REGEXP . SUBMATCH) or a function which should return the name of the mode.
:head-mode
and :tail-mode
specify the major mode which should be used for
head and tail respectively. Special symbol 'host
means that the host mode is
used for head or tail, if 'body
- the mode of the chunk's body. Head and tail
modes default to poly-head-tail-mode
which is a very basic prog mode with no
special powers.
Buffer local polymode-default-inner-mode
can be used to specify the default
mode for body spans either for anonymous (aka mode-less) chunks or when the
major mode cannot be identified. When this variable is nil (the default) either
a host mode or a special poly-fallback-mode
is installed. Which mode is
installed depends on the value of :mode
slot of the innermode configuration
object.
Polymodes
Finally, use the host and inner modes defined earlier to define the
poly-markdown-mode
polymode:
(define-polymode poly-markdown-mode
:hostmode 'poly-markdown-hostmode
:innermodes '(poly-markdown-yaml-metadata-innermode
poly-markdown-fenced-code-innermode))
define-polymode
is similar to the standard Emacs utilities
define-derived-mode
and define-minor-mode
. It accepts several optional
arguments - PARENT
polymode to be derived from, DOC
string and BODY
which
is executed in host and every indirect buffer during the installation of the
chunkmodes. BODY
can be preceded by key-value pairs further refining the
configuration. By far the most common keys are :hostmode
specify the name
(symbol) of the pm-host-chunkmode
object and :innermodes
which is a list of
names of pm-inner-chunkmode
objects. See the documentation of
define-polymode
for further details.
Most polymodes are designed to be used as major modes (i.e. in auto-mode-alist
or buffer-local mode:
cookie). Some polymodes (those with host's :mode
slot
set to nil
) are explicitly designed to be used as minor modes.
Internally, all polymode functions are in fact minor-modes. Thus, you can, for
example, toggle them with M-x poly-XYZ-mode
. Every polymode buffer (base or
indirect) will have this poly-XYZ-mode
minor mode activated in order to
provide the "glue" interface between different chunks. If you want your commands
to be available in all chunks bind them in poly-XYZ-mode-map
or directly in
poly-mode-map
which is the root map of all polymode maps.
A call to define-polymode
creates 3 objects in the background:
poly-XYZ-mode
function which when can be used as major or minor mode.poly-XYZ-mode-map
keymap which inherits fromPARENT
's polymode keymap orpolymode-mode-map
if noPARENT
has been provided.poly-XYZ-polymode
configuration object derived (cloned) frompm-polymode
object or thePARENT
's configuration object ifPARENT
has been provided.
poly-XYZ-mode
will also run poly-XYZ-mode-hook
(and all parents' hooks) in
every chunkmode buffer after the initialization of the chunkmode has been
completed.
Config Objects as Parents
On some occasions the creation of polymode functions for parent objects has
little or no sense. For example poly-latex-root-polymode
is a parent of the
poly-noweb-polymode
but having a dedicated poly-latex-mode
polymode has not
much sense. For such cases PARENT
argument of define-polymode
can also be a
configuration object.
(defvar poly-latex-root-polymode
(pm-polymode :name "latex" :hostmode 'poly-latex-hostmode)
"LaTeX root configuration.")
(define-polymode poly-noweb-mode poly-latex-root-polymode
:innermodes '(pm-inner/noweb)
:keylist '(("<" . poly-noweb-electric-<)))
In a special case when name of PARENT
matches the name of the polymode,
PARENT
is used directly as the configuration object (no clone or defcustom
is performed, as that wouldn't make sense). Therefore, the above definition of
poly-noweb-mode
is equivalent to the following definition with an explicit
poly-noweb-polymode
config:
(defvar poly-noweb-polymode
(clone poly-latex-root-polymode
:name "noweb"
:innermodes '(poly-noweb-innermode)
:keylist '(("<" . poly-noweb-electric-<)))
"Noweb polymode configuration.")
(define-polymode poly-noweb-mode poly-noweb-polymode)