pub struct Model<B> {
pub rows: Vec<Row>,
pub mode: Mode,
pub status: String,
pub dirty: bool,
/* private fields */
}Fields§
§rows: Vec<Row>§mode: Mode§status: StringThe last thing that happened worth saying out loud — almost always a
refusal (rejected: ..., only mapping keys can be renamed).
Empty until something happens. A frontend draws this in whatever it uses for a status line, and an empty string is what lets it draw nothing: a bar that opens holding a word nobody asked for teaches the reader to stop reading it, which is the one thing a refusal channel cannot afford.
dirty: boolImplementations§
Source§impl<B: Backend> Model<B>
impl<B: Backend> Model<B>
Build a model that hides the given top-level mapping keys from the row
projection while keeping them in the document (see
tree::build_rows). For an embedder whose
format reserves some top-level keys (prov/diaryx-managed frontmatter).
Sourcepub fn with_managed(
backend: B,
hidden: Vec<String>,
derived: Vec<String>,
) -> Result<Self>
pub fn with_managed( backend: B, hidden: Vec<String>, derived: Vec<String>, ) -> Result<Self>
Build a model over backend distinguishing the two kinds of managed key:
hidden ones produce no row (edited through another affordance), while
derived ones keep their row but decline every edit (the workspace
maintains them — see derived).
A key in both is hidden: no row means nothing to mark read-only.
Sourcepub fn with_collapsed(
backend: B,
hidden: Vec<String>,
derived: Vec<String>,
collapsed: Vec<Vec<Seg>>,
) -> Result<Self>
pub fn with_collapsed( backend: B, hidden: Vec<String>, derived: Vec<String>, collapsed: Vec<Vec<Seg>>, ) -> Result<Self>
Build a model whose containers at collapsed arrive shut, before the
first row list is ever built.
A document can have one field nobody reads as a list: an index document’s
contents is one row per child — ninety-five of them in a year index,
ahead of the four fields anyone types by hand. Such a section wants to open
as a summary, not a wall you scroll past. Toggling it afterwards through
activate would work, but that is the interactive
door: it moves the selection and rebuilds the row list once per container.
Seeding the set here costs neither — the paths are in place before
reload, so the opening frame is already correct.
A path that names a scalar (or nothing at all) is inert rather than an error, so a caller can name the keys it wants collapsed without first checking which of them turned out to be containers.
Sourcepub fn set_demoted(&mut self, keys: Vec<String>)
pub fn set_demoted(&mut self, keys: Vec<String>)
Name the top-level keys the page projection sinks below the rest.
Out-of-band like set_schema, and for the same
reason: it is presentation the embedder knows and the document does
not. A diaryx host knows part_of is drawn by the sidebar and id by
nothing at all; the fig-backed model reading the same frontmatter has no
way to tell either from a field somebody typed.
Adds to the derived keys already demoted rather than replacing them, so a
caller names only what the constructor did not. Rebuilds the pages, so
the next page already reflects it.
Root keys, matched exactly. A path is demoted when its first segment is one of these, so naming a container demotes everything under it.
Sourcepub fn is_demoted(&self, path: &[Seg]) -> bool
pub fn is_demoted(&self, path: &[Seg]) -> bool
Whether the node at path sits under a demoted top-level key — the
page projection’s own is_derived.
Sourcepub fn set_schema(&mut self, schema: Schema)
pub fn set_schema(&mut self, schema: Schema)
Inject a schema out-of-band — the embedder precedent, mirroring
with_hidden. For a host whose backend does not
supply one but that knows the governing schema (a diaryx host feeding a
fig-backed frontmatter block plus its resolved workspace config).
Sourcepub fn rule_at(&self, path: &[Seg]) -> Option<&FieldRule>
pub fn rule_at(&self, path: &[Seg]) -> Option<&FieldRule>
The schema rule governing the node at path, if any — for a frontend
deciding a widget (a picker for an enum field) or presentation.
Sourcepub fn root_kind(&self) -> &'static str
pub fn root_kind(&self) -> &'static str
The kind of the document root, for a frontend deciding how to add a
top-level entry: "map", "seq", or "scalar".
How many of the hidden top-level keys are actually present in the document — for a “N managed fields” affordance.
Sourcepub fn is_derived(&self, path: &[Seg]) -> bool
pub fn is_derived(&self, path: &[Seg]) -> bool
Whether the node at path sits under a workspace-maintained (derived)
top-level key — for a frontend rendering it read-only rather than as an
editable control. Edits to it are declined at the commit funnel regardless.
Sourcepub fn addable_fields(&self) -> Vec<&FieldRule> ⓘ
pub fn addable_fields(&self) -> Vec<&FieldRule> ⓘ
The schema-declared top-level fields the document does not yet carry — what an “add field” affordance offers, so a declared field is reachable before it exists.
Rows are projected from the document
(build_rows), so a field the schema declares
but the document omits has no row and is otherwise unreachable: the user
would have to know the key and type it exactly. This closes that gap —
it is the schema’s half of the row list, and the reason a declared type
is worth writing down for a field that is empty.
Only a rule addressing exactly one top-level key names an addable field: an each-item or subtree rule governs within a field rather than naming one. Hidden (managed) keys are never offered — the embedder reserves those. Order follows the schema’s own rule order, so a caller can present them as declared.
Sourcepub fn source_snapshot(&self) -> String
pub fn source_snapshot(&self) -> String
The canonical serialized document — what the embedder writes on save.
Sourcepub fn backend(&self) -> &B
pub fn backend(&self) -> &B
The backend, for backend-specific reads (e.g. a prov backend’s body).
Sourcepub fn backend_mut(&mut self) -> &mut B
pub fn backend_mut(&mut self) -> &mut B
The backend, for backend-specific operations that do not change the metadata tree flower renders (e.g. replacing a prov document’s prose body). An op that does change the metadata leaves the view stale — go through the model’s own edit methods for those.
pub fn set_status(&mut self, s: impl Into<String>)
Sourcepub fn mark_saved(&mut self)
pub fn mark_saved(&mut self)
Clear the dirty flag after the embedder has persisted the source.
Sourcepub fn selected_path(&self) -> Option<Vec<Seg>>
pub fn selected_path(&self) -> Option<Vec<Seg>>
The path of whatever is selected in the active view.
The seam that lets one set of edit operations serve both projections: an
edit is a path plus a value, and which list the user picked that path from
is not something commit should have to know.
Sourcepub fn select_row(&mut self, index: usize)
pub fn select_row(&mut self, index: usize)
Put the tree cursor on index, clamped to the row list.
Switches to the tree projection first, and that is the point of the
method rather than a side effect. A row index is a coordinate in the row
list a caller last rendered; it names nothing on a page. A host driving
both surfaces — a metadata pane beside a settings page — would otherwise
hand a row index to a model still standing in the page projection, where
the very next delete_selected reads the page
cursor and quietly removes a different node.
So the vocabularies assert. Every method that establishes a cursor names
the projection its coordinates belong to (page_enter
and the rest do the same for pages), and the methods that merely read a
cursor stay neutral — a delete deletes what is selected, in whichever view
the user is actually looking at.
A no-op when the model is already in the tree.
pub fn move_down(&mut self)
pub fn move_up(&mut self)
Sourcepub fn expand_or_enter(&mut self)
pub fn expand_or_enter(&mut self)
l: expand a collapsed container, else step into its first child.
Sourcepub fn collapse_or_leave(&mut self)
pub fn collapse_or_leave(&mut self)
h: collapse an expanded container, else step out to the parent row.
Sourcepub fn set_view(&mut self, view: ViewMode)
pub fn set_view(&mut self, view: ViewMode)
Switch projection, carrying the cursor across so the node you were on in one view is the node you are on in the other.
Without that, switching would be a jump cut: you fold down to one key in the tree, switch to pages, and land at the top of the root page with no idea where your key went. Carrying the selection makes the two views two ways of looking at one position, which is the only reading under which having both is worth it.
Sourcepub fn toggle_view(&mut self)
pub fn toggle_view(&mut self)
Toggle between the tree and the page view.
Sourcepub fn parent_page(&self) -> &Page
pub fn parent_page(&self) -> &Page
The page one level out — what a two-pane frontend draws on the left. Empty
when focus is the root, which has no parent.
Sourcepub fn focus(&self) -> &[Seg]
pub fn focus(&self) -> &[Seg]
The container the page view is listing. Empty is the document root.
Sourcepub fn page_selected(&self) -> usize
pub fn page_selected(&self) -> usize
The index of the selected item on page.
Sourcepub fn pages_would_degenerate(&self) -> bool
pub fn pages_would_degenerate(&self) -> bool
Whether a two-pane layout would waste one pane on this document.
A document whose root has nothing to drill into — a flat list of keys, a sequence of scalars — has no navigation to put in a sidebar, and splitting the width for it would cost half the room and buy nothing. A frontend checks this to fall back to a single full-width pane.
Sourcepub fn focus_on(&mut self, path: &[Seg])
pub fn focus_on(&mut self, path: &[Seg])
Point the page view at whichever page lists path, with the cursor on
it — the by-path counterpart to drilling, and how a view switch carries
the selection across.
It searches from the root outward rather than from path inward, because
more than one page can contain a node and the outermost is the right one:
an inlined group’s member is listed on the grandparent’s page (that is what
inlining means), and also on the group’s own page, which is a place page
navigation would never have left you. A path that doesn’t resolve is inert.
Sourcepub fn page_at(&self, path: &[Seg]) -> Page
pub fn page_at(&self, path: &[Seg]) -> Page
The page listing the container at path, without going there.
page is where the user is; this is any other level, built
on demand and thrown away. A frontend whose navigation is a stack needs it:
the OS asks “what is the screen for this path element?” for levels the
model is not focused on, and answering by moving the focus would make
rendering a screen a navigation.
Total, like build_page: a path that doesn’t
resolve, or that names a scalar, yields an empty page.
Sourcepub fn peek_page(&self) -> Option<Page>
pub fn peek_page(&self) -> Option<Page>
The page the selected item would open.
A two-pane frontend showing the root’s categories on the left has nothing
to put on the right until you have drilled into something — and an empty
half-screen is a poor advertisement for splitting the width. Previewing
the selected category’s page fills it with the thing you are about to open
anyway, which is what a settings sidebar does. None for a scalar, which
has no page.
Sourcepub fn page_move_down(&mut self)
pub fn page_move_down(&mut self)
j in the page view.
Sourcepub fn page_move_up(&mut self)
pub fn page_move_up(&mut self)
k in the page view.
Sourcepub fn page_enter(&mut self)
pub fn page_enter(&mut self)
l/Enter in the page view: open the selected container as a page, or
begin editing the selected scalar.
A group header opens too. Its members are already on screen, so opening it shows nothing new — but it is the door to operating on the group as a container (append, insert, reorder) rather than on the members, and a container that is visible but cannot be entered is a worse surprise than a page that repeats what you could already see.
Sourcepub fn page_back(&mut self)
pub fn page_back(&mut self)
h/Esc in the page view: pop back to the page that listed the row you
opened, restoring the cursor to it.
One level out is the wrong answer once a row can compress, for the same
reason it is the wrong left pane
(rebuild_pages): opening exports › journal
deliberately skips the exports page because it holds nothing but that
one row, and handing it back on the way out makes leaving cost two steps
where arriving cost one — on a page whose only row is the name of the
place you just left. So this walks out past every level a row compressed
past, and lands where the row was tapped.
Nothing becomes unreachable by it. A compressed row’s
path is the outermost container, so renaming,
deleting, reordering and adding to exports are all still that row’s ops
on the page this lands on — the skipped page never held anything else.
Sourcepub fn is_collapsed(&self, path: &[Seg]) -> bool
pub fn is_collapsed(&self, path: &[Seg]) -> bool
Whether the container at path is collapsed. Answers for a node with no
row too (one nested inside another collapsed container), which
Row::expanded cannot.
Sourcepub fn set_collapsed(&mut self, path: &[Seg], collapsed: bool)
pub fn set_collapsed(&mut self, path: &[Seg], collapsed: bool)
Collapse or expand the container at path, leaving the selection where the
user put it — the by-path, non-interactive counterpart to
activate.
activate folds the selected row, so driving it from a path means moving
the selection first and putting it back after. This doesn’t: it re-anchors
onto whatever was selected before, and only falls back to path itself when
the selection was a descendant that the fold just took off screen.
A path naming a scalar (or nothing) is inert — see
with_collapsed.
pub fn begin_edit(&mut self)
pub fn edit_push(&mut self, c: char)
pub fn edit_backspace(&mut self)
pub fn edit_cancel(&mut self)
pub fn edit_commit(&mut self)
Sourcepub fn set_value_at(&mut self, path: &[Seg], value: Value)
pub fn set_value_at(&mut self, path: &[Seg], value: Value)
Programmatically replace the value at path (any depth), refreshing the
view. The non-interactive counterpart to edit_commit
— for an embedder or FFI that edits by path rather than through the
selection.
Sourcepub fn set_scalar_text(&mut self, path: &[Seg], text: &str)
pub fn set_scalar_text(&mut self, path: &[Seg], text: &str)
Set the scalar at path from an edit-buffer text, coercing by the
schema’s expected type when known (a str field keeps "123" a string)
and otherwise guessing by literal shape — the by-path, schema-aware analog
of edit_commit. Validation (closed-vocabulary
rejection) still happens at the commit funnel.
Sourcepub fn rename_key(&mut self, path: &[Seg], new_key: &str)
pub fn rename_key(&mut self, path: &[Seg], new_key: &str)
Rename the mapping entry at path to new_key, keeping its value and
re-anchoring the selection onto the renamed entry. A no-op (with a status
hint) when path doesn’t end in a key — a sequence item has no key. The
backend rejects a name that collides with an existing sibling key.
Sourcepub fn insert_key(&mut self, map_path: &[Seg], key: &str, value: Value)
pub fn insert_key(&mut self, map_path: &[Seg], key: &str, value: Value)
Insert key = value into the mapping at map_path, selecting the new
entry. A frontend offers this on a map container; the backend rejects a
duplicate key or a non-mapping target, leaving the document untouched.
Sourcepub fn insert_key_text(&mut self, map_path: &[Seg], key: &str, text: &str)
pub fn insert_key_text(&mut self, map_path: &[Seg], key: &str, text: &str)
Insert key = text into the mapping at map_path, coercing text by the
type the schema declares for the new entry and otherwise guessing by literal
shape — the insert-shaped analog of
set_scalar_text.
Prefer this to insert_key whenever the value comes
from a user’s text: a caller that shape-guesses on its own writes 2026 as
an integer into a field the schema declares str, and gets no say from the
schema it is otherwise honoring everywhere else.
Sourcepub fn append_item(&mut self, seq_path: &[Seg], value: Value)
pub fn append_item(&mut self, seq_path: &[Seg], value: Value)
Append value to the sequence at seq_path, selecting the new item.
Sourcepub fn append_item_text(&mut self, seq_path: &[Seg], text: &str)
pub fn append_item_text(&mut self, seq_path: &[Seg], text: &str)
Append text to the sequence at seq_path, coercing it by the type the
schema declares for the sequence’s items and otherwise guessing by literal
shape — the append-shaped analog of
set_scalar_text.
The item’s type comes from the rule matching the item path (an each-item or
subtree rule), not from the rule on the list itself: tags is a seq, its
items are str.
Sourcepub fn move_selected_up(&mut self)
pub fn move_selected_up(&mut self)
Move the selected row one place earlier among its siblings — a sequence item via fig’s array-move, a mapping entry via a one-swap reorder.
Sourcepub fn move_selected_down(&mut self)
pub fn move_selected_down(&mut self)
Move the selected row one place later among its siblings.
Sourcepub fn value_at(&self, path: &[Seg]) -> Option<&Value>
pub fn value_at(&self, path: &[Seg]) -> Option<&Value>
The value the document currently holds at path (the whole tree for the
empty path), or None when the path doesn’t resolve — for a frontend
reading a row’s value without reaching for the backend.
Sourcepub fn seq_len(&self, path: &[Seg]) -> usize
pub fn seq_len(&self, path: &[Seg]) -> usize
The length of the sequence at path (0 for a non-sequence) — the index an
append will land at.
Sourcepub fn delete_selected(&mut self)
pub fn delete_selected(&mut self)
x: delete the selected mapping entry or sequence item.