403 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			403 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|   | # 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](http://www.loveshack.ukfsn.org/emacs/multi-mode.el) gets full | ||
|  | credit for this idea. | ||
|  | 
 | ||
|  | - [Glossary of Terms](#glossary-of-terms) | ||
|  | - [Class Hierarchy](#class-hierarchy) | ||
|  |   - [Polymodes](#polymodes) | ||
|  |   - [Chunkmodes](#chunkmodes) | ||
|  | - [Defining New Polymodes](#defining-new-polymodes) | ||
|  |   - [One Predefined Innermode](#one-predefined-innermode) | ||
|  |   - [Multiple Predefined Innermodes](#multiple-predefined-innermodes) | ||
|  |   - [Multiple Automatically Detected Innermodes](#multiple-automatically-detected-innermodes) | ||
|  | - [Visually debugging polymodes](#visually-debugging-polymodes)  | ||
|  | - [Defining Literate Programming Backends](#defining-backends) | ||
|  |   - [Weavers](#weavers) | ||
|  |   - [Exporters](#exporters) | ||
|  |   - [Tanglers](#tanglers) | ||
|  | - [Internals](#internals) | ||
|  |   - [API](#api) | ||
|  |   - [Initialization of polymodes](#initialization-of-polymodes) | ||
|  | 
 | ||
|  | ## Glossary of Terms
 | ||
|  | 
 | ||
|  | Assume the following `org-mode` file: | ||
|  | 
 | ||
|  | ```org | ||
|  | * 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-mode` | ||
|  |    example 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_src` is 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` (= *b*ody chunk) and hbtchunk | ||
|  |    (= *h*ead + *b*ody + *t*ail spans). In the above example, org-mode emacs-lisp | ||
|  |    chunk starts with `#+begin_src` and ends with `#+end_src` (inclusively). | ||
|  |  - **polymode** is overloaded with three concurrent meanings which we will | ||
|  |    disambiguate from the context: | ||
|  |    1. Like emacs plain modes, polymodes represent an _abstract idea_ of a | ||
|  |       collection of related functionality that is available in emacs buffers. | ||
|  |    2. 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. | ||
|  |    3. 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 variable `pm/polymode`. There are several types | ||
|  |       of polymode objects. See [hierarchy](#class-hierarchy) below. | ||
|  |  - **chunkmode** refers to one of the following: | ||
|  |    1. An abstract idea of the functionality available in chunks of the same type | ||
|  |       (e.g. `org-mode chunk`, `emacs-lisp chunk`). | ||
|  |    2. Emacs mode function (e.g. `org-mode`), or a set of such functions (e.g. | ||
|  |       `pm-head-tail-mode` for header/tail + `emacs-lisp-mode` for the chunk's | ||
|  |       body) what instantiate all of the required functionality of the plain | ||
|  |       emacs modes embodies by that chunk. | ||
|  |    3. Object of `pm-chunkmode` class. This object represents the behavior of the | ||
|  |       chunkmode and is stored in a buffer-local variable `pm/chunkmode`. There | ||
|  |       are several types of chunkmode objects. See [hierarchy](#class-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. | ||
|  | 
 | ||
|  |   | ||
|  | ## 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](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 class `pm-bchunkmode`, | ||
|  |   see [Chunkmodes](#chunkmodes)). | ||
|  | 
 | ||
|  | Currently there are three subclasses of `pm-polymode`: | ||
|  | 
 | ||
|  | - `pm-polymode-one` - used for polymdoes with only one predefined innermode. It | ||
|  |   extends `pm-polymode` with one slot - `:innermode` - which is a name of the | ||
|  |   inner chunkmode (typically objects of class `pm-hbtchunkmode`). | ||
|  | - `pm-polymode-multi` - used for polymodes with multiple predefined inner | ||
|  |   modes. It extends `pm-polymode` with `:innermodes` list that contains names of | ||
|  |   predefined `pm-hbtchunkmode` objects. | ||
|  | - `pm-polymode-multi-auto` - used for polymodes with multiple dynamically | ||
|  |   discoverable chunkmodes. It extends `pm-polymode-multi` with `:auto-innermode` | ||
|  |   slot (typically an object of class `pm-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-mode` etc) | ||
|  | - `:indent-offset`, `:font-lock-narrow`, `:adjust-face`etc - configuration options. | ||
|  |     | ||
|  | Currently, there are three sub classes of `pm-chunkmode`: | ||
|  | 
 | ||
|  | 1. `pm-bchunkmode` - represents the mode of plain body chunks | ||
|  |    (bchunks). These objects are commonly used to represent functionality in | ||
|  |    host chunks and are instances of `pm-bchunkmode`. Currently it doesn't | ||
|  |    add any new slots to its parent class `pm-chunkmode`. | ||
|  | 
 | ||
|  | 2. `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` extends | ||
|  |    `pm-chunkmode` with additional slots, most importantly: | ||
|  |     * `head-mode` and `tail-mode`: names of emacs-modes for header/tail of the | ||
|  |       chunk | ||
|  |     * `head-reg` and `tail-reg`: regular expressions or functions to detect the | ||
|  |       header/tail | ||
|  | 
 | ||
|  | 3. `pm-hbtchunkmode-auto` - represents chunkmodes for which the mode type is not | ||
|  |    predefined and must be computed at runtime. This class extends | ||
|  |    `pm-hbtchunkmode` with `retriver-regexp`, `retriver-num` and | ||
|  |    `retriver-function` which 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](poly-noweb.el). First define the latex hostmode: | ||
|  | 
 | ||
|  | ```lisp | ||
|  | (defcustom pm-host/latex | ||
|  |   (pm-bchunkmode "latex" :mode 'latex-mode) | ||
|  |   "Latex host chunkmode" | ||
|  |   :group 'hostmodes | ||
|  |   :type 'object) | ||
|  | ``` | ||
|  | 
 | ||
|  | Then define the noweb innermode: | ||
|  | 
 | ||
|  | ```lisp | ||
|  | (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: | ||
|  | 
 | ||
|  | ```lisp | ||
|  | (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](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](poly-R.el)): | ||
|  | 
 | ||
|  | ```lisp | ||
|  | (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](poly-markdown.el)). | ||
|  | 
 | ||
|  | ```lisp | ||
|  | ;; 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-f` Toggle font-locking (`pm-debug-toggle-fontification`) | ||
|  |   - `M-n M-h` Map through all spans and briefly blink each span (`pm-debug-map-over-spans-and-highlight`) | ||
|  |   - `M-n M-i` Highlight current span and display more info (`pm-debug-info-on-span`) | ||
|  | 
 | ||
|  | <img src="../img/debug.png"/> | ||
|  | 
 | ||
|  | 
 | ||
|  | ## 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. <!-- Actual instances have --> | ||
|  | <!-- "/" in their name --> | ||
|  | 
 | ||
|  | Buffer local objects: | ||
|  | 
 | ||
|  |    - `pm/type` | ||
|  |    - `pm/chunkmode` | ||
|  |    - `pm/polymode` | ||
|  | 
 | ||
|  | Generics: | ||
|  | 
 | ||
|  |    - `pm-initialize`  | ||
|  |    - `pm-get-buffer-create` | ||
|  |    - `pm-select-buffer` | ||
|  |    - `pm-get-span` | ||
|  |    - `pm-indent-line` | ||
|  |    - `pm-get-adjust-face` | ||
|  | 
 | ||
|  | Utilities: | ||
|  | 
 | ||
|  |    - `pm-get-innermost-span` | ||
|  |    - `pm-map-over-spans` | ||
|  |    - `pm-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: | ||
|  | 
 | ||
|  |    1. assign the config object into local `pm/polymode` variable | ||
|  |    2. clone the `pm-chunkmode` object specified by `:hostmode` slot of | ||
|  |    `pm-polymode` | ||
|  |    3. initialize hostmode by running the actual function in `:mode` slot of the | ||
|  |    hostmode object. | ||
|  |    4. store hostmode object into local `pm/chunkmode` variable | ||
|  |    5. set local variable `pm/type` to `'host` | ||
|  |    6. run `pm-polymode`'s `:init-functions` as normal hooks | ||
|  |    7. run `pm--setup-buffer` which is common setup function used internally to | ||
|  |       set `font-lock` and a range of other stuff | ||
|  |    8. 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. | ||
|  | 
 |