Skip to content

7. History and reading problems

Contents of FBL 0.17

7.1 Undo and redo

Each open body has one history of edits, shared by every reading of it (section 9). An edit records its splices, the bytes each replaced, and the digest of the whole body after it. Undo applies the inverse splices of the most recent edit; redo applies the edit's splices again. A new edit clears the redo stack.

(FBL 0.7) A write to a declared block with history: false (section 8.1) is applied at once and is not a step of the history: undo and redo pass it by, and it leaves the redo stack as it is. It changes only the lines of its block, so an undo or a redo of an edit made before it is applied with its registration offsets moved by what such writes added or removed before them, and the comparison of section 7.2 leaves those blocks' lines out.

A rule or edit with undo: "snapshot" records the whole body before the edit instead; its undo replaces the whole body with that snapshot, as one splice of the whole range under the operation of the edit's first splice. Snapshots are for edits whose inverse would be many splices, such as a removal cascading through a large file; the result is the same bytes either way.

7.2 Drift

Before applying an undo or a redo, the host MUST compare the body on disk (or in the host's buffer, when another editor in the same host holds it) with the bytes the history expects: for an undo, the body after the edit; for a redo, the body before it. When they differ, the body has drifted: the host MUST refuse the undo or redo with a reason ("The file has changed since this edit, so it cannot be undone."), MUST NOT write, and SHOULD offer to reload.

(FBL 0.7) Before applying an edit, the host MUST compare the body on disk, or in the host's buffer as above, with the bytes the edit was planned against. When they differ, the host MUST refuse the edit with a reason ("The file has changed since this edit was made, so it was not written."), MUST NOT write, and MUST read the body again. Section 7.3 then applies: the history is cleared as for any external change.

7.3 External changes

A host watches every open body. When a body changes from outside and the host has no unsaved edits for it, the host reads it again, applies the difference to every open reading (elements matched by id keep their view data), and clears the body's history. When the host has unsaved edits, it tells the user and offers two choices: reload (the unsaved edits are lost) or keep the editor's version, which writes the whole of the editor's bytes as one edit that can itself be undone. A reload that finds the body unreadable keeps the last readable model on screen, read-only, with the finding; a deleted body closes its readings.

7.4 Tolerant reading

Reading MUST NOT fail on the content of a body. Every entry a rule matches but cannot read (a missing required slot, a value that does not convert to the attribute's type and has no fallback (section 5.2), a sequence item that is not a mapping) is an unreadable entry: it is reported as DISL's std.unreadableEntry at its source location, it produces no element (or an element with the readable attributes, when the binding's language says so through DISL's tolerant loading), and its bytes are kept untouched. Other entries are read as usual.

FBL's own finding codes:

Code Severity When
fbl.unbound-statement warning A lines, blocks or (FBL 0.7) bullets statement no rule matches, when the binding's unmatched is report.
fbl.dangling-reference warning A reference or relation end names nothing (section 5.4).
fbl.header-mismatch warning The header is missing or has another value (section 5.6).
fbl.duplicate-key warning A json object or yaml mapping repeats a key; the first is bound. (FBL 0.11) Also two keys of one view of a legacy layout that match one id (section 8.7).
fbl.unconverted-value warning (FBL 0.5) A value does not convert to its attribute's type and the attribute is read as its fallback (section 5.2), unless the fallback's report is false. Its location is the value's span, and its detail carries attribute and the written text.
fbl.missing-body error A registration's body does not exist (section 8.2).
fbl.missing-resource warning (FBL 0.10) A registration's resource header names a resource the body does not hold (section 8.2). Its location is the header's line in the registration.
fbl.stale-view-data info A registration stores view data for an id the body no longer has (section 8.5).
fbl.unknown-header info A registration has a header neither FBL nor the binding declares (section 8.1).
fbl.unknown-block info (FBL 0.7) A registration has a block neither FBL nor the binding declares (section 8.1). It is kept as written.
fbl.unclosed-block warning (FBL 0.11) A block of a grammar body read with recover.nested is still open at the end of the body (section 4.9). Its location is the line and column of its open token.
fbl.unmatched-close warning (FBL 0.11) A close token closes no block; it is unbound (section 4.9). Its location is the token.
fbl.unclosed-comment warning (FBL 0.11) A token of a terminal named in recover.unclosed does not end with its text; it runs to the end of the body (section 4.9). Its location is its first line.
fbl.unparsed-text warning (FBL 0.9) A grammar body with recover holds text that does not parse as a unit: from the unit's first byte to the end of the synchronising token after the place reading stopped, or to the end of the body (section 4.9). The text is kept and binds nothing. Its location is the line and column where reading stopped, and its length that of the skipped text from there.

Every finding carries a DISL source location: file relative to the subject with / separators (for a file body, the body's own name), and the line, column and length of the entry's own span. A finding about something that is not drawn uses DISL's subject.

7.5 The unreadable body

A body is unreadable when it is not valid UTF-8, when it is not well-formed in its family (a YAML or JSON syntax error, XML that is not well-formed, unbalanced braces), when a required header is missing, or when a plugin reports it unreadable. (FBL 0.7) A bullets body is never unreadable for its family (section 4.8): only invalid UTF-8 and a missing required header make it unreadable. (FBL 0.9) A grammar body is not well-formed when its grammar does not read it, or text in it is matched by no terminal the parser's state can shift; with recover it is never unreadable for its family, and is read in part (section 4.9). An unreadable body opens as an empty, read-only model with one finding, DISL's std.unparseable, located at the problem, which replaces all other findings. It is never written. For a file body the body is the primary file, and std.unparseable stops rule evaluation (DISL §8.6); a folder subject has no primary file (section 10.3).

Referenced from