DOM structure

The JavaScript code works by DOM manipulations of a top-level div element, and assume that element has a certain well-behaved form.

domterm-element ::= <div class="domterm"> domterm-toplevel </div>

A top-level “DomTerm window” is a <div> element whose class is domterm.

domterm-toplevel ::= internal-div-elements domterm-buffer+ spacer*

You don’t need to create domterm-toplevel - it is created and managed by DomTerm, based on data from the back-end.

domterm-buffer ::= <div class="dt-buffer" buffer="name"> block-content* </div>

There are one or two domterm-buffer elements, for respectively the “normal” and “alternative” screen buffer. These contain all the “actual” content. If there is a single buffer, the name is "main only"; if there are two buffers, the respective values for name are "main" and "alternate".


One or more invisible <div> elements used by the implementation.

spacer ::= <div class="domterm-spacer"/>

Blank space at the bottom, used to support scrolling.

block-content ::= logical-line | <div> block-content* </div> | opaque-line
logical-line := <div class="domterm-pre"> line-content* hard-nl </pre>
   | <pre> line-content* hard-nl </pre>
   | <p> line-content* hard-nl </p>

A logical-line is one or more “rows” (separated by soft-lines), consisting of character and other inline data, ending with hard-nl representing an explicit newline.

The intent is that <div class="domterm-pre">, <pre> and <p> are treated logically the same, but <div class="domterm-pre"> (or <pre>) will be monospace, while <p> can use other fonts and styling. The line-breaking algorithm uses the width of the line, not the number of characters, so it should also work for <p> elements.

Normal terminal output will create <div class="domterm-pre"> elements, rather than <pre>, because Copy command on some browsers (at least current Firefox) adds extra newlines.

line-content ::= text | <span> line-content* </span> | soft-nl | input-line | other-content
hard-nl ::= <span line="hard">&#A;</span>

An explicit newline. Has a "\n" text node as its sole child.

soft-nl ::= <span line="soft"></span>

An implicit newline, inserted by DomTerm when a line overflows. Has no explicit text content, but CSS adds a "\n" as before content. CSS by default also add a continuation arrow.

input-line ::= <span id="input1" std="input" contenteditable="true"> text </span>

The location of the input cursor. In char-mode the text is empty. In line-mode contains the current input line. Also used for previous input lines, but without contenteditable set. Referenced by the inputLine field of the DomTerm object.


A non-empty block-level element that isn’t navigable at the level of rows and columns. For example a <table>. It is treated as a single empty line.

Lines and columns

This section discusses the mapping between the nested DOM structure and the terminal’s lines and columns.

In a well-formed tree, all line-breaks have the form of <span> element with a line attribute. The child of a <span> element is either a text node consisting of only a newline, or there are no children (and a newline is added using CSS styling). Either way it is treated as a single line-break.

Inserting HTML may also include <br> elements and text containing newlines. These are not handled consistently. Ideally, we should convert newlines in pre-style elements to <span line="hard"> elements.

The last child of a non-empty block-level element (<div>, <pre> etc) should be either another block-level element or a line-break. (We could also allow a <span> whose last element is a line-break, but isn’t clear if this is useful or desirable.) Inserting HTML should insert if needed final line-break (though it currently doesn’t). Alternatively, we can treat a missing line-break as an implicit line-break; currently this is not handled consistently.

A “line” is then the text and elements between two (explicit or implicit) line-breaks.

“Opaque elements” include <img>, <svg>, <object>, and <iframe>. These are treated as single character, taking up a single “column”.

The “characters” of a line are those in text nodes in the line, plus opaque elements.

Command groups

A command group is the set of input and output lines for a single user command.

command-group ::= <div class="command-group"> command-input-line+ command-output? </div>

Usually there is a single command-input-line, but there may be more than one if there are continuation lines.

command-input-line ::= <pre> <span std="prompt"> prompt-text </span> <span std="input"> input-text </span> </pre>

A command-input-line is logical-line which (at least normally) has the form of a prompt followed by the typed input command.

command-output ::= <div class="command-output"> block-content+ </div>

The output from the command. Normally, each block-content is a logical-line. The <div class="command-output"> element may have the domterm-hidden attribute if it is hideable.

Hide/show buttons

A hide/show button is a clickable toggle “button” that controls whether certain “associated output” is shown or hidden. For example the “associated output” of a shell command could be the set of output lines from the command. Initially, the output is in the shown state, and the button displays a “hide” icon. Clicking the button will hide output lines, as well as changing the button to display a “show” icon. Clicking the button again changes the icon to the “hide” icon and unhides the output lines.

<span std="hider" [domterm-hiding="true" or "false"]> hide-icon </span>

This is a hide/show button. The hide-icon is either empty or one of the strings with odd-numbered index in DomTerm’s showHideMarkers property. The value of the showHideMarkers property is an array of strings, where the even-numbers elements are “show” icons, and the following odd-numbered elements are the corresponding “hide” icons. It is suggested (but not required) that these icons be single graphic characters. Good choices are "\u25B6" ( “black right-pointing triangle”) for “show”, and "\u25BC" ( “black down-pointing triangle”) for “hide”.

The attribute domterm-hiding must be "true" or "false"; if missing it defaults to "false". Its value is flipped on each click,

If you are unsatisfied with the existing icon choices you can either set showHideMarkers to other strings, or you can use CSS to change the look of the icons. For example to use [-] and [+] use these CSS rules:

span[std="hider"][domterm-hiding="true"]:after { content: "[+]" }
span[std="hider"]:after { content: "[-]" }

In this case, you probably want the hide-icon text in the <span> to be empty.

The “associated output” for a hide/show button is the set of sibling elements following the button, as well as sibling elements of the button’s parent (assuming that parent is a <pre> or <p> element). Only elements that have the domterm-hidden attribute are affected. Hiding is done by changing the value the domterm-hidden from "false" to "true"; un-hiding changes it back to "false". This is using a CSS style rule that sets the display property of an element to none when domerm-hidden is true

A future extension would allow lazy associated output: The initial state is hidden, and the back-end does not provide the output until it is requested, by clicking the hide/show button. Lazy output is useful for displaying and inspecting large (or even infinite) data structures, such as as a directory hierarchy or a complex object graph.

Alternate screen buffers

Xterm supports an “alternate screen buffer” which is used by character-based programs like emacs. DomTerm allows an arbitrary number of buffers (not just two), with the most recent (bottom-most) being the active one. Each buffer is a <div class="dt-buffer"> element. An alternate buffer has a buffer="alternate" attribute. It is the sibling of the normal screen buffer, which has a buffer attribute valued "main". The "dt-buffer" element contains one or more logical-lines. Returning to the normal screen deletes the <div> for the alternative screen, along with all of its contents, and changes the buffer attribute back to "main only".

Pretty-printing and line-breaking

<span class="pprint-group"> contents </span>

A “logical block” of content that should be printed together, on the same line, if possible. If there is a prefix, it precedes the "pprint-group" element. If there is a per-line prefix, it is in a <span class="pprint-prefix"> preceding element. If there is a block suffix, it follows the "pprint-group" element.

<span class="pprint-prefix"> per-line-prefix </span>

A per-line prefix. This must be the previous sibling of a <span class="pprint-group"> element. It is displayed just before the logical group, and also for each continuation line, at the same indentation.

<span class="pprint-indent" delta="num-chars" />
<span class="pprint-indent" block-delta="num-chars" />
<span class="pprint-indent">text"</span>

Specify extra indentation to add to following continuation lines (in current group). (These pprint-indent elements are non-visible.) A delta="num-chars" changes the indentation to num-chars characters relative to the current horizontal position. A block-delta="num-chars" changes the indentation to num-chars characters relative to the start of the current logical block. Specifying text children specifies text as an extra per-line prefix. The extra indentation is reset at the end of the logical block.

<span line="kind" />

Represents a conditional newline of the specified kind, which can be fill, linear, miser (currently the same as fill), or required.

<span line="kind" [breaking="yes"]> [pre-break-child] [non-break-child] [newline] indentation* [post-break-child] </span>
pre-break-child ::= <span class="pprint-pre-break">contents</span>
post-break-child ::= <span class="pprint-post-break">contents</span>
non-break-child ::= <span class="pprint-non-break">contents</span>
indentation ::= <span class="pprint-indentation">..</span> | text

A generalized optional line-break, where kind is one of "linear", "fill", "miser" (currently same as "fill"), or "required". The breaking attribute is set when a line-break should be shown; the attribute is removed when the break is no longer wanted.

You can specify contents to insert before the break (when there is a break) with a pprint-pre-break element. Similarly, content after the break can be specified using a pprint-post-break element. To specify contents to be used when there is no break, use a pprint-non-break element.

A newline is inserted when the line is actually breaking (though not for soft line-breaks). The indentation, if any, is a mix of cloned outer pprint-indent elements and text that represent actual calculated indentatation. They automatically inserted before the post-break-content when the line is actually breaking, and removed if the line-break is removed. A pprint-indentation element is a (visible) clone of a (non-visible) outer pprint-indent element.

The caret node

<span std="caret">caret-text</span
<span std="caret" value="caret-text"></span

This is used to display the caret position. If the caret is a block or underline, then the caret-text is the character at the caret that is styled with inverse video or an underline. If the caret is at the end of the line, then the caret-text is a single space, and we use a value attribute, because the space is not actually part of the line. If the caret is followed by other text (on the same line), then the caret-text is a text node consisting of the first character of the following text (which is removed from the following text).

If the caret is a vertical-bar then there may still be a caret element, but the caret-text is empty.

Predictive update

If you type a printing character (while in char-mode), DomTerm sends the character to the process, which will then echo (display) it. If there is a slow network connection the delay between when you type the character and the echo can be disconcerting and slow down your typing. To avoid this, DomTerm will tentatively display it right after you type it, without waiting for the echo. The tentative text is displayed in a pending element, as a new-text text child:

<span class="pending" old-text="old-text">new-text</span>

Text (specifically the new-text) is by default displayed with a light gray background to show that it is tentative.

Typing the Left or Right arrow keys may create a Pending element where both the old-text and the new-text are the same string - the text that was moved over. Typing Delete or Backspace has just an old-text attribute for the text that has been (tentatively) deleted. Either Left or Backspace adds a direction="Left" attribute; either Right or Delete adds a direction="Right" attribute.

There is usually at most a single pending element, but in principle there can be many. A pending node may contain children that are nested pending or std="caret" elements.

Before output is processed (or after a timeout) all existing pending nodes are “undone” (in reverse order) so the DOM state matches that expected by the application: All new-text elements are reverted back to the old-text values, and the caret may also be adjusted. Typically the received output wil be “echo” that has the effect of updating the state to match the pending operations - but now as definite updates from the application, rather than pending.