about summary refs log tree commit diff stats
path: root/doc
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-04-24 00:36:20 +0200
committerbptato <nincsnevem662@gmail.com>2024-04-24 00:42:43 +0200
commit62944ac7abc6e37475739a1667ed5a0240fedf66 (patch)
tree21553d4ae539fb5da252dd63daf1b58b9ec6df79 /doc
parent054345a34830a5e34aa722303e07d609c218f36c (diff)
downloadchawan-62944ac7abc6e37475739a1667ed5a0240fedf66.tar.gz
Update docs
Diffstat (limited to 'doc')
-rw-r--r--doc/api.md663
-rw-r--r--doc/architecture.md12
-rw-r--r--doc/config.md524
-rw-r--r--doc/protocols.md20
4 files changed, 838 insertions, 381 deletions
diff --git a/doc/api.md b/doc/api.md
new file mode 100644
index 00000000..d13d8649
--- /dev/null
+++ b/doc/api.md
@@ -0,0 +1,663 @@
+<!-- MANON
+% cha-api(5) | Chawan's command API
+MANOFF -->
+
+# Chawan's command API
+
+As described in <!-- MANOFF -->the [config](config.md) documentation<!-- MANON -->
+<!-- MANON **cha-config**(5) MANOFF -->, keypress combinations can be bound to
+actions.
+
+An action can be either a JavaScript expression, or a command defined in the
+`[cmd]` section of config.toml.  For example, the following works:
+
+```
+gpn = 'n => pager.alert(n)' # e.g. 2gpn prints `2' to the status line
+```
+
+Note however, that JavaScript functions must be called with an appropriate
+`this` value. Unfortunately, this also means that the following does not work:
+
+```
+gpn = 'pager.alert' # broken!!!
+```
+
+To work around this limitation, actions have to wrap the target function in a
+closure, as above. However, this has very poor reusability; for more complex
+actions, you would have to copy and paste the entire function every time you
+re-bind it or call it from a different function.
+
+To fix this, it is possible to define a command in the `[cmd]` section:
+
+```toml
+[cmd.my.namespace]
+showNumber = 'n => pager.alert(n)'
+```
+
+`my.namespace` can be anything you want; it is to avoid collisions when
+including multiple configs. Avoid setting it to `pager` or `line`, because these
+are used by the default config.
+
+Now you can call `cmd.my.namespace.showNumber()` from any other function, or
+just include it in an action:
+
+```toml
+'gpn' = 'cmd.my.namespace.showNumber'
+```
+
+## Interfaces
+
+### Client
+
+The global object (`globalThis`) implements the `Client` interface. Documented
+functions of this are:
+
+Following properties (functions/getters) are defined by `Pager`:
+
+<table>
+
+<tr>
+<th>Property</th>
+<th>Description</th>
+</tr>
+
+<tr>
+<td>`quit()`</td>
+<td>Exit the browser.</td>
+</tr>
+
+<tr>
+<td>`suspend()`</td>
+<td>Temporarily suspend the browser, by delivering the client process a
+SIGTSTP signal.<br>
+Note: this suspends the entire process group.</td>
+</tr>
+
+</table>
+
+`Client` also implements various web standards normally available on the
+`Window` object on websites, e.g. fetch().  Note however that it does *not* give
+access to JS objects in buffers, so e.g. `globalThis.document` is not available.
+
+### Pager
+
+`Pager` is a separate interface from `Client` that gives access to the pager
+(i.e. browser chrome). It is accessible as `globalThis.pager`, or simply
+`pager`.
+
+Following properties (functions/getters) are defined by `Pager`:
+
+<table>
+
+<tr>
+<th>Property</th>
+<th>Description</th>
+</tr>
+
+<tr>
+<td>`load(url)`</td>
+<td>Put the specified address into the URL bar, and optionally load it.<br>
+Note that this performs auto-expansion of URLs, so Chawan will expand any
+matching omni-rules (e.g. search), try to open schemeless URLs with the default
+scheme/local files, etc.<br>
+Opens a prompt with the current URL when no parameters are specified; otherwise,
+the string passed is displayed in the prompt. If this string ends with a newline
+(e.g. `pager.load("about:chawan\n")`), the URL is loaded directly.</td>
+</tr>
+
+<tr>
+<td>`loadSubmit(url)`</td>
+<td>Act as if `url` had been input into the address bar.<br>
+Same as `pager.load(url + "\n")`.</td>
+</tr>
+
+<tr>
+<td>`gotoURL(url)`</td>
+<td>Go to the specified URL immediately (without a prompt). This differs from
+`load` and `loadSubmit` in that it *does not* try to correct the URL.<br>
+Use this for loading automatically retrieved (i.e. non-user-provided) URLs.</td>
+</tr>
+
+<tr>
+<td>`dupeBuffer()`</td>
+<td>Duplicate the current buffer by loading its source to a new buffer.</td>
+</tr>
+
+<tr>
+<td>`discardBuffer()`</td>
+<td>Discard the current buffer, and move back to its previous sibling buffer,
+or if that doesn't exist, to its parent. If the current buffer is a root buffer
+(i.e. it has no parent), move to the next sibling buffer instead.</td>
+</tr>
+
+<tr>
+<td>`discardTree()`</td>
+<td>Discard all child buffers of the current buffer.</td>
+</tr>
+
+<tr>
+<td>`reload()`</td>
+<td>Open a new buffer with the current buffer's URL, replacing the current
+buffer.</td>
+</tr>
+
+<tr>
+<td>`reshape()`</td>
+<td>Reshape the current buffer (=render the current page anew.)</td>
+</tr>
+
+<tr>
+<td>`redraw()`</td>
+<td>Redraw screen contents. Useful if something messed up the display.</td>
+</tr>
+
+<tr>
+<td>`toggleSource()`</td>
+<td>If viewing an HTML buffer, open a new buffer with its source. Otherwise,
+open the current buffer's contents as HTML.</td>
+</tr>
+
+<tr>
+<td>`lineInfo()`</td>
+<td>Display information about the current line.</td>
+</tr>
+
+<tr>
+<td>`searchForward()`</td>
+<td>Search for a string in the current buffer.</td>
+</tr>
+
+<tr>
+<td>`searchBackward()`</td>
+<td>Search for a string, backwards.</td>
+</tr>
+
+<tr>
+<td>`isearchForward()`</td>
+<td>Incremental-search for a string, highlighting the first result.</td>
+</tr>
+
+<tr>
+<td>`isearchBackward()`</td>
+<td>Incremental-search and highlight the first result, backwards.</td>
+</tr>
+
+<tr>
+<td>`gotoLine(n?)`</td>
+<td>Go to the line passed as the first argument.<br>
+If no arguments were specified, an input window for entering a line is
+shown.</td>
+</tr>
+
+<tr>
+<td>`searchNext(n = 1)`</td>
+<td>Jump to the nth next search result.</td>
+</tr>
+
+<tr>
+<td>`searchPrev(n = 1)`</td>
+<td>Jump to the nth previous search result.</td>
+</tr>
+
+<tr>
+<td>`peek()`</td>
+<td>Display an alert message of the current URL.</td>
+</tr>
+
+<tr>
+<td>`peekCursor()`</td>
+<td>Display an alert message of the URL or title under the cursor. Multiple
+calls allow cycling through the two. (i.e. by default, press u once -> title,
+press again -> URL)</td>
+</tr>
+
+<tr>
+<td>`ask(prompt)`</td>
+<td>Ask the user for confirmation. Returns a promise which resolves to a
+boolean value indicating whether the user responded with yes.<br>
+Can be used to implement an exit prompt like this:
+```
+q = 'pager.ask("Do you want to exit Chawan?").then(x => x ? pager.quit() : void(0))'
+```
+</td>
+</tr>
+
+<tr>
+<td>`askChar(prompt)`</td>
+<td>Ask the user for any character.<br>
+Like `pager.ask`, but the return value is a character.</td>
+</tr>
+
+<tr>
+<td>`saveLink()`</td>
+<td>Save URL pointed to by the cursor.</td>
+</tr>
+
+<tr>
+<td>`saveSource()`</td>
+<td>Save the source of the current buffer.</td>
+</tr>
+
+<tr>
+<td>`extern(cmd, options = {setenv: true, suspend: true, wait: false})`
+</td>
+<td>Run an external command `cmd`. The `$CHA_URL` and `$CHA_CHARSET` variables
+are set when `options.setenv` is true. `options.suspend` suspends the pager
+while the command is being executed, and `options.wait` makes it so the user
+must press a key before the pager is resumed.<br>
+Returns true if the command exit successfully, false otherwise.<br>
+Warning: this has a bug where the output is written to stdout even if suspend
+is true. Redirect to /dev/null in the command if this is not desired. (This
+will be fixed in the future.)</td>
+</tr>
+
+<tr>
+<td>`externCapture(cmd)`</td>
+<td>Like extern(), but redirect the command's stdout string into the
+result. null is returned if the command wasn't executed successfully, or if
+the command returned a non-zero exit value.</td>
+</tr>
+
+<tr>
+<td>`externInto(cmd, ins)`</td>
+<td>Like extern(), but redirect `ins` into the command's standard input stream.
+`true` is returned if the command exits successfully, otherwise the return
+value is `false`.</td>
+</tr>
+
+<tr>
+<td>`externFilterSource(cmd, buffer = null, contentType = null)`</td>
+<td>Redirects the specified (or if `buffer` is null, the current) buffer's
+source into `cmd`.<br>
+Then, it pipes the output into a new buffer, with the content type `contentType`
+(or, if `contentType` is null, the original buffer's content type).<br>
+Returns `undefined`. (It should return a promise; TODO.)</td>
+</tr>
+
+<tr>
+<td>`buffer`</td>
+<td>Getter for the currently displayed buffer. Returns a `Buffer` object; see
+below.</td>
+</tr>
+
+</table>
+
+### Buffer
+
+Each buffer is exposed as an object that implements the `Buffer` interface.  To
+get a reference to the currently displayed buffer, use `pager.buffer`.
+
+Important: there exists a quirk of questionable value on pager, where accessing
+properties that do not exist on the pager will dispatch those to the current
+buffer (`pager.buffer`). So if you see e.g. `pager.url`, that is actually
+equivalent to `pager.buffer.url`, because `Pager` has no `url` getter.
+
+Following properties (functions/getters) are defined by `Buffer`:
+
+<table>
+
+<tr>
+<th>Property</th>
+<th>Description</th>
+</tr>
+
+<tr>
+<td>`cursorUp(n = 1)`</td>
+<td>Move the cursor upwards by n lines, or if n is unspecified, by 1.</td>
+</tr>
+
+<tr>
+<td>`cursorDown(n = 1)`</td>
+<td>Move the cursor downwards by n lines, or if n is unspecified, by 1.</td>
+</tr>
+
+<tr>
+<td>`cursorLeft(n = 1)`</td>
+<td>Move the cursor to the left by n cells, or if n is unspecified, by 1.</td>
+</tr>
+
+<tr>
+<td>`cursorRight(n = 1)`</td>
+<td>Move the cursor to the right by n cells, or if n is unspecified, by 1.</td>
+</tr>
+
+<tr>
+<td>`cursorLineBegin()`</td>
+<td>Move the cursor to the first cell of the line.</td>
+</tr>
+
+<tr>
+<td>`cursorLineTextStart()`</td>
+<td>Move the cursor to the first non-blank character of the line.</td>
+</tr>
+
+<tr>
+<td>`cursorLineEnd()`</td>
+<td>Move the cursor to the last cell of the line.</td>
+</tr>
+
+<tr>
+<td>`cursorNextWord()`, `cursorNextViWord()`, `cursorNextBigWord()`</td>
+<td>Move the cursor to the beginning of the next [word](#word-types).</td>
+</tr>
+
+<tr>
+<td>`cursorPrevWord()`, `cursorPrevViWord()`, `cursorPrevBigWord()`</td>
+<td>Move the cursor to the end of the previous [word](#word-types).</td>
+</tr>
+
+<tr>
+<td>`cursorWordEnd()`, `cursorViWordEnd()`, `cursorBigWordEnd()`</td>
+<td>Move the cursor to the end of the current [word](#word-types), or if already
+there, to the end of the next word.</td>
+</tr>
+
+<tr>
+<td>`cursorWordBegin()`, `cursorViWordBegin()`, `cursorBigWordBegin()`</td>
+<td>Move the cursor to the beginning of the current [word](#word-types), or if
+already there, to the end of the previous word.</td>
+</tr>
+
+<tr>
+<td>`cursorNextLink()`, `cursorPrevLink()`</td>
+<td>Move the cursor to the beginning of the next/previous clickable
+element.</td>
+</tr>
+
+<tr>
+<td>`cursorPrevParagraph(n = 1)`</td>
+<td>Move the cursor to the beginning of the nth next paragraph.</td>
+</tr>
+
+<tr>
+<td>`cursorNextParagraph(n = 1)`</td>
+<td>Move the cursor to the end of the nth previous paragraph.</td>
+</tr>
+
+<tr>
+<td>`cursorNthLink(n = 1)`</td>
+<td>Move the cursor to the nth link of the document.</td>
+</tr>
+
+<tr>
+<td>`cursorRevNthLink(n = 1)`</td>
+<td>Move the cursor to the nth link of the document, counting backwards
+from the document's last line.</td>
+</tr>
+
+<tr>
+<td>`pageUp(n = 1)`, `pageDown(n = 1)`, `pageLeft(n = 1)`,
+`pageRight(n = 1)`</td>
+<td>Scroll up/down/left/right by n pages.</td>
+</tr>
+
+<tr>
+<td>`halfPageDown(n = 1)`</td>
+<td>Scroll forwards by n half pages.</td>
+</tr>
+
+<tr>
+<td>`halfPageUp(n = 1)`</td>
+<td>Scroll backwards by n half pages.</td>
+</tr>
+
+<tr>
+<td>`halfPageLeft(n = 1)`</td>
+<td>Scroll to the left by n half pages.</td>
+</tr>
+
+<tr>
+<td>`halfPageUp(n = 1)`</td>
+<td>Scroll to the right by n half pages.</td>
+</tr>
+
+<tr>
+<td>`scrollUp(n = 1)`, `scrollDown(n = 1)`, `scrollLeft(n = 1)`,
+`scrollRight(n = 1)`</td>
+<td>Scroll up/down/left/right by n lines.</td>
+</tr>
+
+<tr>
+<td>`click()`</td>
+<td>Click the HTML element currently under the cursor.</td>
+</tr>
+
+<tr>
+<td>`cursorFirstLine()`, `cursorLastLine()`</td>
+<td>Move to the first/last line in the buffer.</td>
+</tr>
+
+<tr>
+<td>`cursorTop()`</td>
+<td>Move to the first line on the screen. (Equivalent to H in vi.)</td>
+</tr>
+
+<tr>
+<td>`cursorMiddle()`</td>
+<td>Move to the line in the middle of the screen. (Equivalent to M in vi.)</td>
+</tr>
+
+<tr>
+<td>`cursorBottom()`</td>
+<td>Move to the last line on the screen. (Equivalent to L in vi.)</td>
+</tr>
+
+<tr>
+<td>`lowerPage(n = this.cursory)`</td>
+<td>Move cursor to line n, then scroll up so that the cursor is on the
+top line on the screen. (`zt` in vim.)</td>
+</tr>
+
+<tr>
+<td>`lowerPageBegin(n = this.cursory)`</td>
+<td>Move cursor to the first non-blank character of line n, then scroll up
+so that the cursor is on the top line on the screen. (`z<CR>` in vi.)</td>
+</tr>
+
+<tr>
+<td>`centerLine(n = this.cursory)`</td>
+<td>Center screen around line n. (`zz` in vim.)</td>
+</tr>
+
+<tr>
+<td>`centerLineBegin(n = this.cursory)`</td>
+<td>Center screen around line n, and move the cursor to the line's first
+non-blank character. (`z.` in vi.)</td>
+</tr>
+
+<tr>
+<td>`raisePage(n = this.cursory)`</td>
+<td>Move cursor to line n, then scroll down so that the cursor is on the
+top line on the screen. (zb in vim.)</td>
+</tr>
+
+<tr>
+<td>`lowerPageBegin(n = this.cursory)`</td>
+<td>Move cursor to the first non-blank character of line n, then scroll up
+so that the cursor is on the last line on the screen. (`z^` in vi.)</td>
+</tr>
+
+<tr>
+<td>`nextPageBegin(n = this.cursory)`</td>
+<td>If n was given, move to the screen before the nth line and raise the page.
+Otherwise, go to the previous screen's last line and raise the page. (`z+`
+in vi.)</td>
+</tr>
+
+<tr>
+<td>`cursorLeftEdge()`, `cursorMiddleColumn()`, `cursorRightEdge()`</td>
+<td>Move to the first/middle/last column on the screen.</td>
+</tr>
+
+<tr>
+<td>`centerColumn()`</td>
+<td>Center screen around the current column.</td>
+</tr>
+
+<tr>
+<td>`findNextMark(x = this.cursorx, y = this.cursory)`</td>
+<td>Find the next mark after `x`, `y`, if any; and return its id (or null
+if none were found.)</td>
+</tr>
+
+<tr>
+<td>`findPrevMark(x = this.cursorx, y = this.cursory)`</td>
+<td>Find the previous mark before `x`, `y`, if any; and return its id (or null
+if none were found.)</td>
+</tr>
+
+<tr>
+<td>`setMark(id, x = this.cursorx, y = this.cursory)`</td>
+<td>Set a mark at (x, y) using the name `id`.<br>
+Returns true if no other mark exists with `id`. If one already exists,
+it will be overridden and the function returns false.</td>
+</tr>
+
+<tr>
+<td>`clearMark(id)`</td>
+<td>Clear the mark with the name `id`. Returns true if the mark existed,
+false otherwise.</td>
+</tr>
+
+<tr>
+<td>`gotoMark(id)`</td>
+<td>If the mark `id` exists, jump to its position and return true. Otherwise,
+do nothing and return false.</td>
+</tr>
+
+<tr>
+<td>`gotoMarkY(id)`</td>
+<td>If the mark `id` exists, jump to the beginning of the line at
+its Y position and return true. Otherwise, do nothing and return false.</td>
+</tr>
+
+<tr>
+<td>`getMarkPos(id)`</td>
+<td>If the mark `id` exists, return its position as an array where the
+first element is the X position and the second element is the Y position.
+If the mark does not exist, return null.</td>
+</tr>
+
+<tr>
+<td>`markURL()`</td>
+<td>Convert URL-like strings to anchors on the current page.</td>
+</tr>
+
+<tr>
+<td>`url`</td>
+<td>Getter for the buffer's URL. Note: this returns a `URL` object, not a
+string.</td>
+</tr>
+
+<tr>
+<td>`hoverTitle`, `hoverLink`, `hoverImage`</td>
+<td>Getter for the string representation of the element title/link/image
+currently under the cursor. Returns the empty string if no title is found.</td>
+</tr>
+
+</table>
+
+### LineEdit
+
+The line editor at the bottom of the screen is exposed to the JavaScript context
+as `globalThis.line`, or simply `line`, and implements the `LineEdit` interface.
+
+Note that there is no single `LineEdit` object; a new one is created every time
+the line editor is opened, and when the line editor is closed, `globalThis.line`
+simply returns `null`.
+
+Following properties (functions/getters) are defined by `LineEdit`:
+
+<table>
+
+<tr>
+<th>Property</th>
+<th>Description</th>
+</tr>
+
+<tr>
+<td>`submit()`</td>
+<td>Submit line</td>
+</tr>
+
+<tr>
+<td>`cancel()`</td>
+<td>Cancel operation</td>
+</tr>
+
+<tr>
+<td>`backspace()`</td>
+<td>Delete character before cursor</td>
+</tr>
+
+<tr>
+<td>`delete()`</td>
+<td>Delete character after cursor</td>
+</tr>
+
+<tr>
+<td>`clear()`</td>
+<td>Clear text before cursor</td>
+</tr>
+
+<tr>
+<td>`kill()`</td>
+<td>Clear text after cursor</td>
+</tr>
+
+<tr>
+<td>`clearWord()`</td>
+<td>Delete word before cursor</td>
+</tr>
+
+<tr>
+<td>`killWord()`</td>
+<td>Delete word after cursor</td>
+</tr>
+
+<tr>
+<td>`backward()`</td>
+<td>Move cursor back by one character</td>
+</tr>
+
+<tr>
+<td>`forward()`</td>
+<td>Move cursor forward by one character</td>
+</tr>
+
+<tr>
+<td>`prevWord()`</td>
+<td>Move cursor to the previous word by one character</td>
+</tr>
+
+<tr>
+<td>`nextWord()`</td>
+<td>Move cursor to the previous word by one character</td>
+</tr>
+
+<tr>
+<td>`begin()`</td>
+<td>Move cursor to the previous word by one character</td>
+</tr>
+
+<tr>
+<td>`end()`</td>
+<td>Move cursor to the previous word by one character</td>
+</tr>
+
+<tr>
+<td>`escape()`</td>
+<td>Ignore keybindings for next character</td>
+</tr>
+
+<tr>
+<td>`prevHist()`</td>
+<td>Jump to the previous history entry</td>
+</tr>
+
+<tr>
+<td>`nextHist()`</td>
+<td>Jump to the next history entry</td>
+</tr>
+
+</table>
diff --git a/doc/architecture.md b/doc/architecture.md
index cee904e1..b3d2d6ec 100644
--- a/doc/architecture.md
+++ b/doc/architecture.md
@@ -246,18 +246,14 @@ it some time...)
 Also, contents of the start.startup-script option are executed at startup. This
 is used when `cha` is called with the `-r` flag.
 
-There *is* an API, but it's severely underdocumented. Web APIs are exposed to
+There *is* an API, but it's somewhat underdocumented. Web APIs are exposed to
 pager too, but you cannot operate on the DOMs themselves from the pager, unless
 you create one yourself with DOMParser.parseFromString.
 
-[config.md](config.md) describes all functions that are used in the default
-config. However, it does not describe the true nature of the API:
-when pager.someFunction is called and someFunction is not present in pager,
-it dispatches the call to pager.buffer.someFunction (where pager.buffer is a
-reference to the current buffer).
+[config.md](config.md) describes all commands that are used in the default
+config.
 
-For example, all cursor movement commands described in config.md are in fact
-dispatched to the current buffer's container.
+A more complete description of the API can be found in [api.md](api.md).
 
 ### JS in the buffer
 
diff --git a/doc/config.md b/doc/config.md
index 5d584f37..4c2814fd 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -690,36 +690,27 @@ modifiers. Modifiers are the prefixes `C-` and `M-`, which add control or
 escape to the keybinding respectively (essentially making `M-` the same as
 `C-[`). Modifiers can be escaped with the `\` sign.
 
-Examples:
-
-```
-# change URL when Control, Escape and j are pressed
-'C-M-j' = 'pager.load()'
-# go to the first line of the page when g is pressed twice
-'gg' = 'pager.cursorFirstLine()'
-# filter the current buffer's source through rdrview, then display the output in
-# a new buffer when the keys `g' and then `f' are pressed
-# (see https://github.com/eafer/rdrview)
-'gr' = 'pager.externFilterSource("rdrview -H")'
-```
-
-An action is a JavaScript expression called by Chawan every time the keybinding
-is typed in. If an action returns a function, Chawan will also call the
-returned function automatically. So this works too:
+`<action>` is either a command defined in the `[cmd]` section, or a JavaScript
+expression. Here we only describe the pre-defined actions in the default config;
+for a description of the API, please see:
 
-```
-U = '() => pager.load()' # works
-```
+<!-- MANOFF -->
+[the API documentation](api.md).
+<!-- MANON -->
+<!-- MANON
+The API documentation at **cha-api**(5).
+MANOFF -->
 
-Note however, that JavaScript functions must be called with an appropriate
-this value. Unfortunately, this also means that the following does not work:
+Examples:
 
-```
-q = 'pager.load' # broken!!!
+```toml
+# show change URL when Control, Escape and j are pressed
+'C-M-j' = 'cmd.pager.load'
+# go to the first line of the page when g is pressed twice without a preceding
+# number, or to the line when a preceding number is given.
+'gg' = 'cmd.pager.gotoLineOrStart'
 ```
 
-A list of built-in pager functions can be found below.
-
 ### Browser actions
 
 <table>
@@ -730,22 +721,25 @@ A list of built-in pager functions can be found below.
 </tr>
 
 <tr>
-<td>`quit()`</td>
+<td>`cmd.pager.quit`</td>
 <td>Exit the browser.</td>
 </tr>
 
 <tr>
-<td>`suspend()`</td>
-<td>Temporarily suspend the browser (by delivering the client process a
-SIGTSTP signal.)<br>
-Note: this suspends the entire process group, including e.g. buffer processes or
-CGI scripts.</td>
+<td>`cmd.pager.suspend`</td>
+<td>Temporarily suspend the browser<br>
+Note: this also suspends e.g. buffer processes or CGI scripts. So if you are
+downloading something, that will be delayed until you restart the process.</td>
 </tr>
 
 </table>
 
 ### Pager actions
 
+Note: `n` in the following text refers to a number preceding the action.  e.g.
+in `10gg`, n = 10.  If no preceding number is input, then it is left
+unspecified.
+
 <table>
 
 <tr>
@@ -754,497 +748,301 @@ CGI scripts.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorUp(n = 1)`</td>
+<td>`cmd.pager.cursorUp`</td>
 <td>Move the cursor upwards by n lines, or if n is unspecified, by 1.</td>
 </tr>
 <tr>
-<td>`pager.cursorDown(n = 1)`</td>
+<td>`cmd.pager.cursorDown`</td>
 <td>Move the cursor downwards by n lines, or if n is unspecified, by 1.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorLeft(n = 1)`</td>
+<td>`cmd.pager.cursorLeft`</td>
 <td>Move the cursor to the left by n cells, or if n is unspecified, by 1.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorRight(n = 1)`</td>
+<td>`cmd.pager.cursorRight`</td>
 <td>Move the cursor to the right by n cells, or if n is unspecified, by 1.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorLineBegin()`</td>
+<td>`cmd.pager.cursorLineBegin`</td>
 <td>Move the cursor to the first cell of the line.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorLineTextStart()`</td>
+<td>`cmd.pager.cursorLineTextStart`</td>
 <td>Move the cursor to the first non-blank character of the line.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorLineEnd()`</td>
+<td>`cmd.pager.cursorLineEnd`</td>
 <td>Move the cursor to the last cell of the line.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorNextWord()`, `pager.cursorNextViWord()`,
-`pager.cursorNextBigWord()`</td>
+<td>`cmd.pager.cursorNextWord`, `cmd.pager.cursorNextViWord`,
+`cmd.pager.cursorNextBigWord`</td>
 <td>Move the cursor to the beginning of the next [word](#word-types).</td>
 </tr>
 
 <tr>
-<td>`pager.cursorPrevWord()`, `pager.cursorPrevViWord()`,
-`pager.cursorPrevBigWord()`</td>
+<td>`cmd.pager.cursorPrevWord`, `cmd.pager.cursorPrevViWord`,
+`cmd.pager.cursorPrevBigWord`</td>
 <td>Move the cursor to the end of the previous [word](#word-types).</td>
 </tr>
 
 <tr>
-<td>`pager.cursorWordEnd()`, `pager.cursorViWordEnd()`,
-`pager.cursorBigWordEnd()`</td>
+<td>`cmd.pager.cursorWordEnd`, `cmd.pager.cursorViWordEnd`,
+`cmd.pager.cursorBigWordEnd`</td>
 <td>Move the cursor to the end of the current [word](#word-types), or if already
 there, to the end of the next word.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorWordBegin()`, `pager.cursorViWordBegin()`,
-`pager.cursorBigWordBegin()`</td>
+<td>`cmd.pager.cursorWordBegin`, `cmd.pager.cursorViWordBegin`,
+`cmd.pager.cursorBigWordBegin`</td>
 <td>Move the cursor to the beginning of the current [word](#word-types), or if
 already there, to the end of the previous word.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorNextLink()`</td>
-<td>Move the cursor to the beginning of the next clickable element.</td>
+<td>`cmd.pager.cursorPrevLink`</td>
+<td>Move the cursor to the beginning of the previous clickable element.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorPrevLink()`</td>
-<td>Move the cursor to the beginning of the previous clickable element.</td>
+<td>`cmd.pager.cursorNextLink`</td>
+<td>Move the cursor to the beginning of the next clickable element.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorPrevParagraph(n = 1)`</td>
+<td>`cmd.pager.cursorPrevParagraph`</td>
 <td>Move the cursor to the beginning of the nth next paragraph.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorNextParagraph(n = 1)`</td>
+<td>`cmd.pager.cursorNextParagraph`</td>
 <td>Move the cursor to the end of the nth previous paragraph.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorNthLink(n = 1)`</td>
-<td>Move the cursor to the nth link of the document.</td>
-</tr>
-
-<tr>
-<td>`pager.cursorRevNthLink(n = 1)`</td>
+<td>`cmd.pager.cursorRevNthLink`</td>
 <td>Move the cursor to the nth link of the document, counting backwards
 from the document's last line.</td>
 </tr>
 
 <tr>
-<td>`pager.pageDown(n = 1)`</td>
-<td>Scroll down by n pages.</td>
-</tr>
-
-<tr>
-<td>`pager.pageUp(n = 1)`</td>
-<td>Scroll up by n pages.</td>
-</tr>
-
-<tr>
-<td>`pager.pageLeft(n = 1)`</td>
-<td>Scroll to the left by n pages.</td>
-</tr>
-
-<tr>
-<td>`pager.pageRight(n = 1)`</td>
-<td>Scroll to the right n pages.</td>
-</tr>
-
-<tr>
-<td>`pager.halfPageDown(n = 1)`</td>
-<td>Scroll forwards by n half pages.</td>
-</tr>
-
-<tr>
-<td>`pager.halfPageUp(n = 1)`</td>
-<td>Scroll backwards by n half pages.</td>
-</tr>
-
-<tr>
-<td>`pager.halfPageLeft(n = 1)`</td>
-<td>Scroll to the left by n half pages.</td>
-</tr>
-
-<tr>
-<td>`pager.halfPageUp(n = 1)`</td>
-<td>Scroll to the right by n half pages.</td>
-</tr>
-
-<tr>
-<td>`pager.scrollDown(n = 1)`</td>
-<td>Scroll forwards by n lines.</td>
+<td>`cmd.pager.cursorNthLink`</td>
+<td>Move the cursor to the nth link of the document.</td>
 </tr>
 
 <tr>
-<td>`pager.scrollUp(n = 1)`</td>
-<td>Scroll backwards by n lines.</td>
+<td>`cmd.pager.pageUp`, `cmd.pager.pageDown`, `cmd.pager.pageLeft`,
+`cmd.pager.pageRight`</td>
+<td>Scroll up/down/left/right by n pages, or if n is unspecified, by one
+page.</td>
 </tr>
 
 <tr>
-<td>`pager.scrollLeft(n = 1)`</td>
-<td>Scroll to the left by n columns.</td>
+<td>`cmd.pager.halfPageUp`, `cmd.pager.halfPageDown`, `cmd.pager.halfPageLeft`,
+`pager.halfPageUp`</td>
+<td>Scroll up/down/left/right by n half pages, or if n is unspecified, by one
+page.</td>
 </tr>
 
 <tr>
-<td>`pager.scrollRight(n = 1)`</td>
-<td>Scroll to the right by n columns.</td>
+<td>`cmd.pager.scrollUp`, `cmd.pager.scrollDown`, `cmd.pager.scrollLeft`,
+`cmd.pager.scrollRight`</td>
+<td>Scroll up/down/left/right by n lines, or if n is unspecified, by one
+line.</td>
 </tr>
 
 <tr>
-<td>`pager.click()`</td>
+<td>`cmd.pager.click`</td>
 <td>Click the HTML element currently under the cursor.</td>
 </tr>
 
 <tr>
-<td>`pager.load(url)`</td>
-<td>Put the specified address into the URL bar, and optionally load it.<br>
-Note that this performs auto-expansion of URLs, so Chawan will expand any
-matching omni-rules (e.g. search), try to open schemeless URLs with the default
-scheme/local files, etc.<br>
-Opens a prompt with the current URL when no parameters are specified; otherwise,
-the string passed is displayed in the prompt. If this string ends with a newline
-(e.g. `pager.load("about:chawan\n")`), the URL is loaded directly.</td>
-</tr>
-
-<tr>
-<td>`pager.loadSubmit(url)`</td>
-<td>Act as if `url` had been input into the address bar.<br>
-Same as `pager.load(url + "\n")`.</td>
+<td>`cmd.pager.load`</td>
+<td>Open the current address in the URL bar.</td>
 </tr>
 
 <tr>
-<td>`pager.gotoURL(url)`</td>
-<td>Go to the specified URL immediately (without a prompt). This differs from
-`load` and `loadSubmit` in that it *does not* try to correct the URL.<br>
-Use this for loading automatically retrieved (i.e. non-user-provided) URLs.</td>
+<td>`cmd.pager.webSearch`</td>
+<td>Open the URL bar with an arbitrary search engine. At the moment, this is
+DuckDuckGo Lite. (Note: Chawan developers aren't affiliated with DuckDuckGo the
+company or their product in any way.)</td>
 </tr>
 
 <tr>
-<td>`pager.dupeBuffer()`</td>
+<td>`cmd.pager.dupeBuffer`</td>
 <td>Duplicate the current buffer by loading its source to a new buffer.</td>
 </tr>
 
 <tr>
-<td>`pager.discardBuffer()`</td>
+<td>`cmd.pager.discardBuffer`</td>
 <td>Discard the current buffer, and move back to its previous sibling buffer,
 or if that doesn't exist, to its parent. If the current buffer is a root buffer
 (i.e. it has no parent), move to the next sibling buffer instead.</td>
 </tr>
 
 <tr>
-<td>`pager.discardTree()`</td>
+<td>`cmd.pager.discardTree`</td>
 <td>Discard all child buffers of the current buffer.</td>
 </tr>
 
 <tr>
-<td>`pager.reload()`</td>
+<td>`cmd.pager.reload`</td>
 <td>Open a new buffer with the current buffer's URL, replacing the current
 buffer.</td>
 </tr>
 
 <tr>
-<td>`pager.reshape()`</td>
+<td>`cmd.pager.reshape`</td>
 <td>Reshape the current buffer (=render the current page anew.)</td>
 </tr>
 
 <tr>
-<td>`pager.redraw()`</td>
+<td>`cmd.pager.redraw`</td>
 <td>Redraw screen contents. Useful if something messed up the display.</td>
 </tr>
 
 <tr>
-<td>`pager.toggleSource()`</td>
+<td>`cmd.pager.toggleSource`</td>
 <td>If viewing an HTML buffer, open a new buffer with its source. Otherwise,
 open the current buffer's contents as HTML.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorFirstLine()`</td>
-<td>Move to the beginning in the buffer.</td>
-</tr>
-
-<tr>
-<td>`pager.cursorLastLine()`</td>
-<td>Move to the last line in the buffer.</td>
+<td>`cmd.pager.cursorFirstLine`, `cmd.pager.cursorLastLine`</td>
+<td>Move to the beginning/end in the buffer.</td>
 </tr>
 
 <tr>
-<td>`pager.cursorTop()`</td>
+<td>`cmd.pager.cursorTop`</td>
 <td>Move to the first line on the screen. (Equivalent to H in vi.)</td>
 </tr>
 
 <tr>
-<td>`pager.cursorMiddle()`</td>
+<td>`cmd.pager.cursorMiddle`</td>
 <td>Move to the line in the middle of the screen. (Equivalent to M in vi.)</td>
 </tr>
 
 <tr>
-<td>`pager.cursorBottom()`</td>
+<td>`cmd.pager.cursorBottom`</td>
 <td>Move to the last line on the screen. (Equivalent to L in vi.)</td>
 </tr>
 
 <tr>
-<td>`pager.lowerPage(n = pager.cursory)`</td>
-<td>Move cursor to line n, then scroll up so that the cursor is on the
-top line on the screen. (`zt` in vim.)</td>
-</tr>
-
-<tr>
-<td>`pager.lowerPageBegin(n = pager.cursory)`</td>
-<td>Move cursor to the first non-blank character of line n, then scroll up
-so that the cursor is on the top line on the screen. (`z<CR>` in vi.)</td>
-</tr>
-
-<tr>
-<td>`pager.centerLine(n = pager.cursory)`</td>
-<td>Center screen around line n. (`zz` in vim.)</td>
-</tr>
+<td>`cmd.pager.raisePage`, `cmd.pager.raisePageBegin`,
+`cmd.pager.centerLine`, `cmd.pager.centerLineBegin`,
+`cmd.pager.lowerPage`, `cmd.pager.lowerPageBegin`</td>
+<td>If n is specified, move cursor to line n. Then,
 
-<tr>
-<td>`pager.centerLineBegin(n = pager.cursory)`</td>
-<td>Center screen around line n, and move the cursor to the line's first
-non-blank character. (`z.` in vi.)</td>
-</tr>
+* `raisePage` scrolls down so that the cursor is on the top line of the screen.
+  (vi `z<CR>`, vim `zt`.)
+* `centerLine` shifts the screen so that the cursor is in the middle of the
+  screen. (vi `z.`, vim `zz`.)
+* `lowerPage` scrolls up so that the cursor is on the bottom line of the screen.
+  (vi `z-`, vim `zb`.)
 
-<tr>
-<td>`pager.raisePage(n = pager.cursory)`</td>
-<td>Move cursor to line n, then scroll down so that the cursor is on the
-top line on the screen. (zb in vim.)</td>
-</tr>
-
-<tr>
-<td>`pager.lowerPageBegin(n = pager.cursory)`</td>
-<td>Move cursor to the first non-blank character of line n, then scroll up
-so that the cursor is on the last line on the screen. (`z^` in vi.)</td>
+The -`Begin` variants also move the cursor to the line's first non-blank
+character, as the variants originating from vi do.
+</td>
 </tr>
 
 <tr>
-<td>`pager.nextPageBegin(n = pager.cursory)`</td>
-<td>If n was given, move to the screen before the nth line and raise the page.
+<td>`cmd.pager.nextPageBegin`</td>
+<td>If n is specified, move to the screen before the nth line and raise the page.
 Otherwise, go to the previous screen's last line and raise the page. (`z+`
 in vi.)</td>
 </tr>
 
 <tr>
-<td>`pager.cursorLeftEdge()`</td>
-<td>Move to the first column on the screen.</td>
-</tr>
-
-<tr>
-<td>`pager.cursorMiddleColumn()`</td>
-<td>Move to the column in the middle of the screen.</td>
-</tr>
-
-<tr>
-<td>`pager.cursorRightEdge()`</td>
-<td>Move to the last column on the screen.</td>
-</tr>
-
-<tr>
-<td>`pager.centerColumn()`</td>
-<td>Center screen around the current column.</td>
-</tr>
-
-<tr>
-<td>`pager.lineInfo()`</td>
-<td>Display information about the current line.</td>
-</tr>
-
-<tr>
-<td>`pager.searchForward()`</td>
-<td>Search for a string in the current buffer.</td>
-</tr>
-
-<tr>
-<td>`pager.searchBackward()`</td>
-<td>Search for a string, backwards.</td>
-</tr>
-
-<tr>
-<td>`pager.isearchForward()`</td>
-<td>Incremental-search for a string, highlighting the first result.</td>
-</tr>
-
-<tr>
-<td>`pager.isearchBackward()`</td>
-<td>Incremental-search and highlight the first result, backwards.</td>
-</tr>
-
-<tr>
-<td>`pager.gotoLine(n?)`</td>
-<td>Go to the line passed as the first argument.<br>
-If no arguments were specified, an input window for entering a line is
-shown.</td>
-</tr>
-
-<tr>
-<td>`pager.searchNext(n = 1)`</td>
-<td>Jump to the nth next search result.</td>
-</tr>
-
-<tr>
-<td>`pager.searchPrev(n = 1)`</td>
-<td>Jump to the nth previous search result.</td>
+<td>`cmd.pager.previousPageBegin`</td>
+<td>If n is specified, move to the screen before the nth line and raise the
+page.  Otherwise, go to the previous screen's last line and raise the page.
+(`z+` in vi.)</td>
 </tr>
 
 <tr>
-<td>`pager.peek()`</td>
-<td>Display an alert message of the current URL.</td>
+<td>`cmd.pager.cursorLeftEdge`, `cmd.pager.cursorMiddleColumn`,
+`cmd.pager.cursorRightEdge`</td>
+<td>Move to the first/middle/last column on the screen.</td>
 </tr>
 
 <tr>
-<td>`pager.peekCursor()`</td>
-<td>Display an alert message of the URL or title under the cursor. Multiple
-calls allow cycling through the two. (i.e. by default, press u once -> title,
-press again -> URL)</td>
+<td>`cmd.pager.centerColumn`</td>
+<td>Center screen around the current column. (w3m `Z`.)</td>
 </tr>
 
 <tr>
-<td>`pager.ask(prompt)`</td>
-<td>Ask the user for confirmation. Returns a promise which resolves to a
-boolean value indicating whether the user responded with yes.<br>
-Can be used to implement an exit prompt like this:
-```
-q = 'pager.ask("Do you want to exit Chawan?").then(x => x ? pager.quit() : void(0))'
-```
-</td>
+<td>`cmd.pager.lineInfo`</td>
+<td>Display information about the current line on the status line.</td>
 </tr>
 
 <tr>
-<td>`pager.askChar(prompt)`</td>
-<td>Ask the user for any character.<br>
-Like `pager.ask`, but the return value is a character.</td>
+<td>`cmd.pager.searchForward`, `cmd.pager.searchBackward`</td>
+<td>Search for a string in the current buffer, forwards or backwards.</td>
 </tr>
 
 <tr>
-<td>`pager.setMark(id, x = pager.cursorx, y = pager.cursory)`</td>
-<td>Set a mark at (x, y) using the name `id`.<br>
-Returns true if no other mark exists with `id`. If one already exists,
-it will be overridden and the function returns false.</td>
+<td>`cmd.pager.isearchForward`, `cmd.pager.searchBackward`</td>
+<td>Incremental-search for a string, highlighting the first result, forwards or
+backwards.</td>
 </tr>
 
 <tr>
-<td>`pager.clearMark(id)`</td>
-<td>Clear the mark with the name `id`. Returns true if the mark existed,
-false otherwise.</td>
+<td>`cmd.pager.gotoLineOrStart`, `cmd.pager.gotoLineOrEnd`</td>
+<td>If n is specified, jump to line n. Otherwise, jump to the start/end of the
+page.</td>
 </tr>
 
 <tr>
-<td>`pager.gotoMark(id)`</td>
-<td>If the mark `id` exists, jump to its position and return true. Otherwise,
-do nothing and return false.</td>
+<td>`cmd.pager.searchNext`, `cmd.pager.searchPrev`</td>
+<td>Jump to the nth (or if unspecified, first) next/previous search result.</td>
 </tr>
 
 <tr>
-<td>`pager.gotoMarkY(id)`</td>
-<td>If the mark `id` exists, jump to the beginning of the line at
-its Y position and return true. Otherwise, do nothing and return false.</td>
+<td>`cmd.pager.peek`</td>
+<td>Display a message of the current buffer's URL on the status line.</td>
 </tr>
 
 <tr>
-<td>`pager.getMarkPos(id)`</td>
-<td>If the mark `id` exists, return its position as an array where the
-first element is the X position and the second element is the Y position.
-If the mark does not exist, return null.</td>
+<td>`cmd.pager.peekCursor`</td>
+<td>Display a message of the URL or title under the cursor on the status line.
+Multiple calls allow cycling through the two. (i.e. by default, press u once ->
+title, press again -> URL)</td>
 </tr>
 
 <tr>
-<td>`pager.findNextMark(x = pager.cursorx, y = pager.cursory)`</td>
-<td>Find the next mark after `x`, `y`, if any; and return its id (or null
-if none were found.)</td>
+<td>`cmd.pager.setMark`</td>
+<td>Wait for a character `x` and then set a mark with the ID `x`.</td>
 </tr>
 
 <tr>
-<td>`pager.findPrevMark(x = pager.cursorx, y = pager.cursory)`</td>
-<td>Find the previous mark before `x`, `y`, if any; and return its id (or null
-if none were found.)</td>
+<td>`cmd.pager.gotoMark`, `cmd.pager.gotoMarkY`</td>
+<td>Wait for a character `x` and then jump to the mark with the ID `x` (if it
+exists on the page).<br>
+`gotoMark` sets both the X and Y positions; gotoMarkY only sets the Y
+position.</td>
 </tr>
 
 <tr>
-<td>`pager.markURL()`</td>
+<td>`cmd.pager.markURL`</td>
 <td>Convert URL-like strings to anchors on the current page.</td>
 </tr>
 
 <tr>
-<td>`pager.saveLink()`</td>
-<td>Save URL pointed to by the cursor.</td>
-</tr>
-
-<tr>
-<td>`pager.saveSource()`</td>
-<td>Save the source of the current buffer.</td>
-</tr>
-
-<tr>
-<td>`pager.extern(cmd, options = {setenv: true, suspend: true, wait: false})`
-</td>
-<td>Run an external command `cmd`. The `$CHA_URL` and `$CHA_CHARSET` variables
-are set when `options.setenv` is true. `options.suspend` suspends the pager
-while the command is being executed, and `options.wait` makes it so the user
-must press a key before the pager is resumed.<br>
-Returns true if the command exit successfully, false otherwise.<br>
-Warning: this has a bug where the output is written to stdout even if suspend
-is true. Redirect to /dev/null in the command if this is not desired. (This
-will be fixed in the future.)</td>
-</tr>
-
-<tr>
-<td>`pager.externCapture(cmd)`</td>
-<td>Like extern(), but redirect the command's stdout string into the
-result. null is returned if the command wasn't executed successfully, or if
-the command returned a non-zero exit value.</td>
-</tr>
-
-<tr>
-<td>`pager.externInto(cmd, ins)`</td>
-<td>Like extern(), but redirect `ins` into the command's standard input stream.
-`true` is returned if the command exits successfully, otherwise the return
-value is `false`.</td>
-</tr>
-
-<tr>
-<td>`pager.externFilterSource(cmd, buffer = null, contentType = null)`</td>
-<td>Redirects the specified (or if `buffer` is null, the current) buffer's
-source into `cmd`.<br>
-Then, it pipes the output into a new buffer, with the content type `contentType`
-(or, if `contentType` is null, the original buffer's content type).<br>
-Returns `undefined`. (It should return a promise; TODO.)</td>
-</tr>
-
-<tr>
-<td>`pager.url`</td>
-<td>Getter for the link of the current buffer. Returns a `URL` object.</td>
-</tr>
-
-<tr>
-<td>`pager.hoverLink`</td>
-<td>Getter for the link currently under the cursor. Returns the empty string if
-no link is found.</td>
-</tr>
-
-<tr>
-<td>`pager.hoverTitle`</td>
-<td>Getter for the title currently under the cursor. Returns the empty string if
-no title is found.</td>
+<td>`cmd.pager.saveLink`</td>
+<td>Save resource from the URL pointed to by the cursor to the disk.</td>
 </tr>
 
 <tr>
-<td>`pager.buffer`</td>
-<td>Getter for the currently displayed buffer. Returns a `Buffer` object.</td>
+<td>`cmd.pager.saveSource`</td>
+<td>Save the source of the current buffer to the disk.</td>
 </tr>
 
 </table>
@@ -1260,87 +1058,87 @@ no title is found.</td>
 </tr>
 
 <tr>
-<td>`line.submit()`</td>
+<td>`cmd.line.submit`</td>
 <td>Submit line</td>
 </tr>
 
 <tr>
-<td>`line.cancel()`</td>
+<td>`cmd.line.cancel`</td>
 <td>Cancel operation</td>
 </tr>
 
 <tr>
-<td>`line.backspace()`</td>
+<td>`cmd.line.backspace`</td>
 <td>Delete character before cursor</td>
 </tr>
 
 <tr>
-<td>`line.delete()`</td>
+<td>`cmd.line.delete`</td>
 <td>Delete character after cursor</td>
 </tr>
 
 <tr>
-<td>`line.clear()`</td>
+<td>`cmd.line.clear`</td>
 <td>Clear text before cursor</td>
 </tr>
 
 <tr>
-<td>`line.kill()`</td>
+<td>`cmd.line.kill`</td>
 <td>Clear text after cursor</td>
 </tr>
 
 <tr>
-<td>`line.clearWord()`</td>
+<td>`cmd.line.clearWord`</td>
 <td>Delete word before cursor</td>
 </tr>
 
 <tr>
-<td>`line.killWord()`</td>
+<td>`cmd.line.killWord`</td>
 <td>Delete word after cursor</td>
 </tr>
 
 <tr>
-<td>`line.backward()`</td>
+<td>`cmd.line.backward`</td>
 <td>Move cursor back by one character</td>
 </tr>
 
 <tr>
-<td>`line.forward()`</td>
+<td>`cmd.line.forward`</td>
 <td>Move cursor forward by one character</td>
 </tr>
 
 <tr>
-<td>`line.prevWord()`</td>
+<td>`cmd.line.prevWord`</td>
 <td>Move cursor to the previous word by one character</td>
 </tr>
 
 <tr>
-<td>`line.nextWord()`</td>
+<td>`cmd.line.nextWord`</td>
 <td>Move cursor to the previous word by one character</td>
 </tr>
 
 <tr>
-<td>`line.begin()`</td>
+<td>`cmd.line.begin`</td>
 <td>Move cursor to the previous word by one character</td>
 </tr>
 
 <tr>
-<td>`line.end()`</td>
+<td>`cmd.line.end`</td>
 <td>Move cursor to the previous word by one character</td>
 </tr>
 
 <tr>
-<td>`line.escape()`</td>
+<td>`cmd.line.escape`</td>
 <td>Ignore keybindings for next character</td>
 </tr>
 
 <tr>
-<td>`line.prevHist()`</td>
+<td>`cmd.line.prevHist`</td>
 <td>Jump to the previous history entry</td>
 </tr>
 
 <tr>
-<td>`line.nextHist()`</td>
+<td>`cmd.line.nextHist`</td>
 <td>Jump to the next history entry</td>
 </tr>
 
@@ -1354,11 +1152,11 @@ character. (This means that e.g. `https://` consists of four words: `https`,
 
 ```Examples:
 # Control+A moves the cursor to the beginning of the line.
-'C-a' = 'line.begin()'
+'C-a' = 'cmd.line.begin'
 
 # Escape+D deletes everything after the cursor until it reaches a word-breaking
 # character.
-'M-d' = 'line.killWord()'
+'M-d' = 'cmd.line.killWord'
 ```
 
 ## Appendix
@@ -1366,10 +1164,10 @@ character. (This means that e.g. `https://` consists of four words: `https`,
 ### Regex handling
 
 Regular expressions are currently handled using libregexp which is included in
-QuickJS. This means that all regular expressions should work as in JavaScript.
+QuickJS. This means that all regular expressions work as in JavaScript.
 
-There are two different modes of regex handling in Chawan: "search" mode, and
-"match" mode. "match" mode is used for configurations (meaning in all values
+There are two different modes of regex preprocessing in Chawan: "search" mode,
+and "match" mode. "match" mode is used for configurations (meaning in all values
 in this document described as "regex"). "search" mode is used for the on-page
 search function (using searchForward/isearchForward etc.)
 
@@ -1381,13 +1179,13 @@ with a caret (^) sign or end with an unescaped dollar ($) sign.
 In other words, the following transformations occur:
 
 ```
-^abcd -> ^abcd (no change)
-efgh$ -> efgh$ (no change)
-^ijkl$ -> ^ijkl$ (no change)
-mnop -> ^mnop$ (changed to exact match)
+^abcd -> ^abcd (no change, only beginning is matched)
+efgh$ -> efgh$ (no change, only end is matched)
+^ijkl$ -> ^ijkl$ (no change, the entire line is matched)
+mnop -> ^mnop$ (changed to exact match, the entire line is matched)
 ```
 
-Match mode has no way to toggle JavaScript regex flags.
+Match mode has no way to toggle JavaScript regex flags like `i`.
 
 #### Search mode
 
diff --git a/doc/protocols.md b/doc/protocols.md
index a9ca11e8..2d591bc2 100644
--- a/doc/protocols.md
+++ b/doc/protocols.md
@@ -191,9 +191,8 @@ of the retrieved resource. If an error is encountered, it prints a
 
 ### Adding a new protocol
 
-Here we will add a protocol called "example", so that the URL example:text
-prints "text" after a second of waiting. We will write the adapter in shell
-script.
+Here we will add a protocol called "cowsay", so that the URL cowsay:text
+prints the output of `cowsay text` after a second of waiting.
 
 First, make sure you have a local CGI path `~/cgi-bin` set up in your
 `~/.config/chawan/config.toml`:
@@ -207,7 +206,7 @@ It is also possible to just put your CGI scripts to
 edits in your config. But it seems more convenient to use a dedicated cgi-bin in
 your home directory.
 
-`mkdir ~/cgi-bin`, and create a CGI script in `~/cgi-bin` called `example.cgi`:
+`mkdir ~/cgi-bin`, and create a CGI script in `~/cgi-bin` called `cowsay.cgi`:
 
 ```sh
 #!/bin/sh
@@ -226,9 +225,10 @@ printf 'Status: 200' # HTTP OK
 printf 'Cha-Control: ControlDone\n'
 # As in HTTP, you must send an empty line before the body.
 printf '\n'
-# Now, print the body. We just take the path passed to the URL; urimethodmap
-# sets this as MAPPED_URI_PATH.
-printf '%s' "$MAPPED_URI_PATH"
+# Now, print the body. We take the path passed to the URL; urimethodmap
+# sets this as MAPPED_URI_PATH. This is URI-encoded, so we also run the urldec
+# utility on it.
+cowsay "$(printf '%s\n' "$MAPPED_URI_PATH" | "$CHA_LIBEXEC_DIR"/urldec)"
 ```
 
 Now, create a ".urimethodmap" file in your `$HOME` directory.
@@ -236,11 +236,11 @@ Now, create a ".urimethodmap" file in your `$HOME` directory.
 Then, enter into it the following:
 
 ```
-example:	/cgi-bin/example.cgi
+cowsay:		/cgi-bin/cowsay.cgi
 ```
 
-Now try `cha example:Hello_world`. If you did everything correctly, it should
-wait one second, then print "Hello_world".
+Now try `cha cowsay:Hello,%20world.`. If you did everything correctly, it should
+wait one second, then print a cow saying "Hello, world.".
 
 <!-- MANON