Skip to main content

Model

Struct Model 

Source
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: String

The 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: bool

Implementations§

Source§

impl<B: Backend> Model<B>

Source

pub fn new(backend: B) -> Result<Self>

Build a model over backend.

Source

pub fn with_hidden(backend: B, hidden: Vec<String>) -> Result<Self>

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

pub fn schema(&self) -> Option<&Schema>

The schema governing the document, if any.

Source

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.

Source

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".

Source

pub fn hidden_present(&self) -> usize

How many of the hidden top-level keys are actually present in the document — for a “N managed fields” affordance.

Source

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.

Source

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.

Source

pub fn source_snapshot(&self) -> String

The canonical serialized document — what the embedder writes on save.

Source

pub fn backend(&self) -> &B

The backend, for backend-specific reads (e.g. a prov backend’s body).

Source

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.

Source

pub fn set_status(&mut self, s: impl Into<String>)

Source

pub fn mark_saved(&mut self)

Clear the dirty flag after the embedder has persisted the source.

Source

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.

Source

pub fn selected(&self) -> usize

The selected row of the tree projection — an index into rows.

Source

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.

Source

pub fn move_down(&mut self)

Source

pub fn move_up(&mut self)

Source

pub fn expand_or_enter(&mut self)

l: expand a collapsed container, else step into its first child.

Source

pub fn collapse_or_leave(&mut self)

h: collapse an expanded container, else step out to the parent row.

Source

pub fn view(&self) -> ViewMode

Which projection is active.

Source

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.

Source

pub fn toggle_view(&mut self)

Toggle between the tree and the page view.

Source

pub fn page(&self) -> &Page

The page currently being listed.

Source

pub fn root_page(&self) -> &Page

The root’s page.

Source

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.

Source

pub fn focus(&self) -> &[Seg]

The container the page view is listing. Empty is the document root.

Source

pub fn page_selected(&self) -> usize

The index of the selected item on page.

Source

pub fn page_item(&self) -> Option<&PageItem>

The selected page item, if the page has any.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn page_move_down(&mut self)

j in the page view.

Source

pub fn page_move_up(&mut self)

k in the page view.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn activate(&mut self)

Enter/Space: toggle a container’s expansion, or edit a scalar.

Source

pub fn begin_edit(&mut self)

Source

pub fn edit_push(&mut self, c: char)

Source

pub fn edit_backspace(&mut self)

Source

pub fn edit_cancel(&mut self)

Source

pub fn edit_commit(&mut self)

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn append_item(&mut self, seq_path: &[Seg], value: Value)

Append value to the sequence at seq_path, selecting the new item.

Source

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.

Source

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.

Source

pub fn move_selected_down(&mut self)

Move the selected row one place later among its siblings.

Source

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.

Source

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.

Source

pub fn delete_selected(&mut self)

x: delete the selected mapping entry or sequence item.

Auto Trait Implementations§

§

impl<B> Freeze for Model<B>
where B: Freeze,

§

impl<B> RefUnwindSafe for Model<B>
where B: RefUnwindSafe,

§

impl<B> Send for Model<B>
where B: Send,

§

impl<B> Sync for Model<B>
where B: Sync,

§

impl<B> Unpin for Model<B>
where B: Unpin,

§

impl<B> UnsafeUnpin for Model<B>
where B: UnsafeUnpin,

§

impl<B> UnwindSafe for Model<B>
where B: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.