7. History and reading problems
Contents of FBL 0.8
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.
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.
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.
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.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.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. |
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.
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. 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).