| .. | ||
| poly-base.el | ||
| poly-c.el | ||
| poly-erb.el | ||
| poly-markdown.el | ||
| poly-noweb.el | ||
| poly-org.el | ||
| poly-R.el | ||
| poly-slim.el | ||
| readme.md | ||
Developing with Polymode
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. Dave Love's multi-mode.el gets full credit for this idea.
- Glossary of Terms
- Class Hierarchy
- Defining New Polymodes
- Visually debugging polymodes
- Defining Literate Programming Backends
- Internals
Glossary of Terms
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 - a syntactically homogeneous fragment of text. In the
org-modeexample the org span starts at the beginning of the file ends with (but does not include)#+begin_src. Header span is#+begin_src emacs-lisp :var tbl='(). The emacs lisp code span follows next.#+end_srcis the tail span. - sub-mode - an emacs mode from inside a span.
- chunk is a well delimited fragment of text that consists of one or more
spans. Most common types of chunks are
bchunk(= body chunk) and hbtchunk (= head + body + tail spans). In the above example, org-mode emacs-lisp chunk starts with#+begin_srcand ends with#+end_src(inclusively). - polymode is overloaded with three concurrent meanings which we will
disambiguate from the context:
- Like emacs plain 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-polymodeclass. 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-mode chunk,emacs-lisp chunk). - Emacs mode function (e.g.
org-mode), or a set of such functions (e.g.pm-head-tail-modefor header/tail +emacs-lisp-modefor the chunk's body) what instantiate all of the required functionality of the plain emacs modes embodies by that chunk. - Object of
pm-chunkmodeclass. 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.
- An abstract idea of the functionality available in chunks of the same type
(e.g.
- 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.
Class Hierarchy
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.
Polymodes
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-polymodewith 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-polymodewith:innermodeslist that contains names of predefinedpm-hbtchunkmodeobjects.pm-polymode-multi-auto- used for polymodes with multiple dynamically discoverable chunkmodes. It extendspm-polymode-multiwith:auto-innermodeslot (typically an object of classpm-hbtchunkmode-auto).
Chunkmodes
Most important user visible slots of the root class pm-chunkmode are:
:mode- symbol of corresponding emacs plain mode (e.g.html-mode,latex-modeetc):indent-offset,:font-lock-narrow,:adjust-faceetc - 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-hbtchunkmodeextendspm-chunkmodewith additional slots, most importantly:head-modeandtail-mode: names of emacs-modes for header/tail of the chunkhead-regandtail-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-hbtchunkmodewithretriver-regexp,retriver-numandretriver-functionwhich can be used to retrive the mode name from the header of the inner chunk.
Defining New Polymodes
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.
One Predefined Innermode
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.
Multiple Predefined Innermodes
No examples yet. Web-mode would probably qualify.
Multiple Automatically Detected Innermodes
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)
Visually Debugging Polymodes
After defining polymodes you can visually inspect if the polymode does what you
intended by activating globalized minor pm-debug minor mode with M-x pm-debug-mode. When pm-debug-mode is active the current span will be
highlighted and brief info displayed in the minibuffer.
Currently defined commands are:
M-n M-fToggle font-locking (pm-debug-toggle-fontification)M-n M-hMap through all spans and briefly blink each span (pm-debug-map-over-spans-and-highlight)M-n M-iHighlight current span and display more info (pm-debug-info-on-span)
Defining Backends
Weavers
todo
Exporters
todo
Tanglers
todo
Internals
Warning: Following description is subject to change and might not be up-to-date.
API
All API classes and methods are named with pm- prefix.
Buffer local objects:
pm/typepm/chunkmodepm/polymode
Generics:
pm-initializepm-get-buffer-createpm-select-bufferpm-get-spanpm-indent-linepm-get-adjust-face
Utilities:
pm-get-innermost-spanpm-map-over-spanspm-narrow-to-span
Initialization of polymodes
Note: This description is obsolete. Internals have changed.
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/polymodevariable - clone the
pm-chunkmodeobject specified by:hostmodeslot ofpm-polymode - initialize hostmode by running the actual function in
:modeslot of the hostmode object. - store hostmode object into local
pm/chunkmodevariable - set local variable
pm/typeto'host - run
pm-polymode's:init-functionsas normal hooks - run
pm--setup-bufferwhich is common setup function used internally to setfont-lockand 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-get-buffer-create generic which, in turn, creates pm-chunkmode
object and the associated indirect buffer.