Polymode doesn't keep its modes in a single emacs buffer but in several indirect buffers, as many as different modes are there in a file. Consequently, polymode is as fast as switching emacs buffers because it never re-installs major modes like other multi-modes do. I am very much indebted to Dave Love's multi-mode.el for this awesome idea.
- Glossary of Terms
- Class Hierarchy
- Defining New Polymodes
- Defining Literate Programming Backends
- Internals
Assume the following org-mode
file:
* emacs lisp code block
#+begin_src emacs-lisp :var tbl='()
(defun all-to-string (tbl)
(if (listp tbl)
(mapcar #'all-to-string tbl)
(if (stringp tbl)
tbl
(format "%s" tbl))))
(all-to-string tbl)
#+end_src
- span is a homogeneous in terms of syntactic content fragment of text. In
the
org-mode
example the org span starts at the beginning of the file till, but not including,#+begin_src
. Header span is#+begin_src emacs-lisp :var tbl='()
. The emacs lisp code span is next, followed by#+end_src
tail span. - chunk is a well delimited fragment of text that consists of one or more
spans. Most common types of chunks are bchunk (plain body chunk) and hbtchunk
(chunk composed of head, body and tail spans). In our example, org-mode span
and org-mode chunk are the same thing and the region that starts with
#+begin_src
and ends with#+end_src
is an org emacs-lisp chunk. - polymode has three concurrent meanings which we will disambiguate from
the context:
- Like emacs modes, polymodes represent an abstract idea of a collection of related functionality that is available in emacs buffers.
- Like emacs modes, polymodes are functions that install a bunch of functionality into emacs buffer. You can use polymodes just as any other emacs major or minor mode.
- Polymodes are objects of
pm-polymode
class. The functionality of each polymode is completely characterized by this object and the methods that act on it. During initialization this object is cloned and its copy is stored in a buffer-local variablepm/polymode
. There are several types of polymode objects. See hierarchy below.
- chunkmode refers to one of the following:
- An abstract idea of the functionality available in chunks of the same type (e.g. org chunks, emacs-lisp chunks).
- Emacs mode function (e.g.
org-mode
), or a collection of such functions (e.g.fundamental-mode
for header/tail andemacs-lisp-mode
for the chunk's body) that instantiate the functionality into the corresponding modes. - Object of
pm-chunkmode
class. This object represents the behavior of the chunkmode and is stored in a buffer-local variablepm/chunkmode
. There are several types of chunkmode objects. See hierarchy below.
- hostmodes and innermodes Chunkmodes could be classified into host and inner chunkmodes (hostmodes and innermodes in short). In the above example org chunkmode is a hostmode and emacs-lisp chunkmode is an innermode.
It is easy to think of the chunkmodes as inter-weaved threads. Host chunkmode is a stretched canvas. Each inner chunkmode is a thread weaved into the hostmode. Visible fragments of the each thread are chunks.
In light of the above metaphor, it is worth emphasizing the distinctions between
chunks
and chunkmodes
. Chunks are fragments of text and there might be
multiple chunks of the same type in the buffer. In contrast, there is only one
chunkmode of some specific type and multiple chunks of this type "share" this
chunkmode.
Polymode package uses eieio
to represent its objects. The root class for all
polymode classes is eieio-instance-inheritor
which provides prototype based
inheritance (in addition to class based). This means that objects instantiated
from polymode classes can be cloned in order to dynamically create a hierarchy
of customizable objects. There are a bunch of such objects already defined, you
can investigate those in polymodes
, hostmodes
, innermodes
customization
groups.
As polymode uses indirect buffers to implement the multi-mode, storing mode functionality in objects (in contrast to buffer local variables) is very convenient strategy for moving stuff around.
Current polymode class hierarchy:
+--eieio-instance-inheritor
| +--pm-root
| +--pm-polymode
| | +--pm-polymode-multi
| | | +--pm-polymode-multi-auto
| | +--pm-polymode-one
| |
| +--pm-chunkmode
| | +--pm-hbtchunkmode
| | | +--pm-hbtchunkmode-auto
| | +--pm-bchunkmode
| |
| +--pm-weaver
| | +--pm-shell-weaver
| | +--pm-callback-weaver
| +--pm-exporter
| +--pm-shell-exporter
| +--pm-callback-exporter
Using Help with EIEIO: Each eieio
class has a corresponding constructor
whose docstring contains a complete description of the class. In emacs 24.4 or
higher you can use C-h f pm-foo RET
to inspect the documentation of the
class. Alternatively either use M-x describe-class pm-foo
or lookup the class
definition directly in polymode-classes.el.
As noted earlier, each polymode is a function that walks and quacks like
standard emacs major mode. Hence, things like poly-XXX-mode-map
and
poly-XXX-mode-hook
work just as expected. Plymode functions are defined with
define-polymode
and can be used in place of emacs standard major or minor
modes.
Each polymode is represented by a customizable pm-polymode
object which fully
characterizes its behavior. During the initialization this config object is
cloned and installed in every new buffer.
The most important slot of root config class pm-polymode
is:
:hostmode
- name of the chunkmode object (typicaly of classpm-bchunkmode
, see Chunkmodes).
Currently there are three subclasses of pm-polymode
:
pm-polymode-one
- used for polymdoes with only one predefined innermode. It extendspm-polymode
with one slot -:innermode
- which is a name of the inner chunkmode (typically objects of classpm-hbtchunkmode
).pm-polymode-multi
- used for polymodes with multiple predefined inner modes. It extendspm-polymode
with:innermodes
list that contains names of predefinedpm-hbtchunkmode
objects.pm-polymode-multi-auto
- used for polymodes with multiple dynamically discoverable chunkmodes. It extendspm-polymode-multi
with:auto-innermode
slot (typically an object of classpm-hbtchunkmode-auto
).
Most important user visible slots of the root class pm-chunkmode
are:
:mode
- symbol of corresponding emacs plain mode (e.g.html-mode
,latex-mode
etc):indent-offset
,:font-lock-narrow
,:adjust-face
etc - configuration options.
Currently, there are three sub classes of pm-chunkmode
:
-
pm-bchunkmode
- represents the mode of plain body chunks (bchunks). These objects are commonly used to represent functionality in host chunks and are instances ofpm-bchunkmode
. Currently it doesn't add any new slots to its parent classpm-chunkmode
. -
hbtchunkmode
- represents the mode of composite head-body-tail chunks. These objects are commonly used to represent the functionality of the innermost chunks of the buffer.pm-hbtchunkmode
extendspm-chunkmode
with additional slots, most importantly:head-mode
andtail-mode
: names of emacs-modes for header/tail of the chunkhead-reg
andtail-reg
: regular expressions or functions to detect the header/tail
-
pm-hbtchunkmode-auto
- represents chunkmodes for which the mode type is not predefined and must be computed at runtime. This class extendspm-hbtchunkmode
withretriver-regexp
,retriver-num
andretriver-function
which can be used to retrive the mode name from the header of the inner chunk.
In order to define a new polymode poly-cool-mode
you first have to define or
clone a chunkmode object to represent the hostmode, and one or more chunkmodes
to represent innermodes. Then define the polymode object pm-poly/cool
pointing
to previously defined host and inner chunkmodes.
There are a lot of polymodes, hostmodes and innermodes already defined. Please reuse those whenever possible.
This is a simplified version of poly-noweb-mode
from
poly-noweb.el. First define the latex hostmode:
(defcustom pm-host/latex
(pm-bchunkmode "latex" :mode 'latex-mode)
"Latex host chunkmode"
:group 'hostmodes
:type 'object)
Then define the noweb innermode:
(defcustom pm-inner/noweb
(pm-hbtchunkmode "noweb"
:head-reg "<<\\(.*\\)>>="
:tail-reg "\\(@ +%def .*\\)$\\|\\(@[ \n]\\)")
"Noweb typical chunk."
:group 'innermodes
:type 'object)
Finally, define the pm-polymode
object and the coresponding polymode function:
(defcustom pm-poly/noweb
(pm-polymode-one "noweb"
:hostmode 'pm-host/latex
:innermode 'pm-inner/noweb)
"Noweb typical polymode."
:group 'polymodes
:type 'object)
(define-polymode poly-noweb-mode pm-poly/noweb)
The hostmode pm-host/latex
from above is already defined in
poly-base.el, so you need not have declared it.
Now, let's assume you want a more specialized noweb mode, say noweb
with R
chunks. Instead of declaring root hostmodes and innermodes again you should
clone existing noweb root object. This is how it is done (from
poly-R.el):
(defcustom pm-inner/noweb+R
(clone pm-inner/noweb :mode 'R-mode)
"Noweb innermode for R"
:group 'innermodes
:type 'object)
(defcustom pm-poly/noweb+R
(clone pm-poly/noweb :innermode 'pm-inner/noweb+R)
"Noweb polymode for R"
:group 'polymodes
:type 'object)
(define-polymode poly-noweb+r-mode pm-poly/noweb+R :lighter " PM-Rnw")
That's it. You simply had to define new innermode and polymode by cloning from
previously defined objects and adjusting :mode
and :innermode
slots
respectively.
No examples yet. Web-mode would probably qualify.
This is an example of markdown polymode (from poly-markdown.el).
;; 1. Define hostmode object
(defcustom pm-host/markdown
(pm-bchunkmode "Markdown" :mode 'markdown-mode)
"Markdown host chunkmode"
:group 'hostmodes
:type 'object)
;; 2. Define innermode object
(defcustom pm-inner/markdown
(pm-hbtchunkmode-auto "markdown"
:head-reg "^[ \t]*```[{ \t]*\\w.*$"
:tail-reg "^[ \t]*```[ \t]*$"
:retriever-regexp "```[ \t]*{?\\(\\(\\w\\|\\s_\\)*\\)"
:font-lock-narrow t)
"Markdown typical chunk."
:group 'innermodes
:type 'object)
;; 3. Define polymode object
(defcustom pm-poly/markdown
(pm-polymode-multi-auto "markdown"
:hostmode 'pm-host/markdown
:auto-innermode 'pm-inner/markdown
:init-functions '(poly-markdown-remove-markdown-hooks))
"Markdown typical configuration"
:group 'polymodes
:type 'object)
;; 4. Define polymode function
(define-polymode poly-markdown-mode pm-poly/markdown)
todo
todo
todo
All API classes and methods are named with pm-
prefix.
Buffer local objects:
pm/type
pm/chunkmode
pm/polymode
Generics:
pm-initialize
pm-get-buffer
pm-select-buffer
pm-install-buffer
pm-get-span
pm-indent-line
pm-get-adjust-face
Utilities:
pm/get-innermost-span
pm/map-over-spans
pm/narrow-to-span
When called, poly-XXX-mode
(created with define-polymode
) clones
pm-poly/XXX
object and calls pm-initialize
generic on it. The actual
initialization depends on concrete type of the pm-polymode
object but these
are the common steps:
- assign the config object into local
pm/polymode
variable - clone the
pm-chunkmode
object specified by:hostmode
slot ofpm-polymode
- initialize hostmode by running the actual function in
:mode
slot of the hostmode object. - store hostmode object into local
pm/chunkmode
variable - set local variable
pm/type
to'host
- run
pm-polymode
's:init-functions
as normal hooks - run
pm--setup-buffer
which is common setup function used internally to setfont-lock
and a range of other stuff - run
poly-XXX-mode-hook
.
Discovery of the spans is done by pm-select-buffer
generic which is commonly
called first by jit-lock
. pm-select-buffer
fist checks if the corresponding
pm-chunkmode
object (and associated indirect buffer) has been already
created. If so, pm-select-buffer
simply selects that buffer. Otherwise, it
calls pm-install-buffer
generic which, in turn, creates pm-chunkmode
object
and the associated indirect buffer.