pub struct Session { /* private fields */ }Expand description
A diagram being edited, the steps behind and ahead of it, and a position among them.
Nothing here changes a session, and the type system says so rather than a rule this file
keeps. No method takes &mut self: every one of them is asked over &self and hands a new
Session back, so a caller that forgets to keep what it was given changes nothing, and a
caller that keeps it still cannot have changed this one. The guarantee §4 of the model makes —
the diagram a session owns is changed through the session and nowhere else — is what the absence
of a &mut self anywhere in this surface means, and it is a stronger answer than the one a
private field was: nothing in this crate can break it, because there is no borrow that would let
it. The price is that a caller has to keep what it is given: discarding the result of
perform changes nothing at all, which is a warning rather than a failure.
The field below is written once on top of that: a command changes the diagram by way of the
steps, never by way of the field.
The field holds the diagram the session was created over, and the steps hold every state after it. A step is the diagram a command left, so the state before the first command is the one state no step holds, and it is the field — which is why nothing writes the field again, and why the diagram in hand is either that field or a step and never anything else. One diagram per step and one to start with, which is the model’s “memory proportional to the diagram times the number of steps”, and each of them held once.
What a session is made of is shareable, and the sharing is what pays for the copies. Two
sessions standing at different positions in one history hold the same VectorSync<Step> and the
same Diagrams, and a new one costs a new position and at most a new leaf. Nothing in the layer
needs two sessions at once today — the demonstration walks one — but this is the property a
caller gets for free the first time one does, and it is why the steps are a persistent sequence
and not a Vec.
Giving a step back moves the position and changes nothing else. undo and
redo are one expression each for that reason, and neither removes a step or
rebuilds a diagram: the state that comes back is the state that was there, held since the command
that made it. Nothing is inverted, and every step stays where it is while the history walks over
them, which is what makes the walk go both ways.
The history is a line, and position is where in it the session stands. It is how many
steps are behind rather than an index into a pair of stacks, so it cannot disagree with either
of them: there is something behind exactly when position is above zero, and something ahead
exactly when it is below steps.len(). A command carried out from behind the end discards what
was ahead, which is the only end a line can discard from without changing what giving a step
back means.
A command that changes nothing records nothing and discards nothing (#285): the session comes back as it was, so a branch is only ever cut by a command that moved the drawing.
The steps are held in the same persistent sequence the diagram holds its figures in, so a
session standing at one place in a history shares every step with one standing at another
rather than copying them — and a step is a whole diagram beside a command, which is what makes
the difference between sharing and copying the difference between 949 B and 40 122 B per step.
That sequence has no truncation, so what was ahead is dropped by a walk of drop_last_mut
rather than in one step: each of those is a trie descent where a Vec was one, and it is paid
only on the branch that carries a command out from behind the end. See kept_first, which is
where that walk is written and tested.
§The absence, shown rather than described
The rule that nothing here changes a session is held by nothing in this file, so it is worth one documentation example that says it out loud: everything up to the last line compiles, and the last line is a method this crate does not have.
use monospace_core::{Buffer, Pos, Size, Window};
use monospace_diagram::Diagram;
use monospace_editing::Session;
let session = Session::new(Diagram::new());
session.diagram().draw(&mut Buffer::new(Window {
origin: Pos { x: 0, y: 0 },
size: Size { width: 1, height: 1 },
}));
session.diagram_mut();Implementations§
Source§impl Session
impl Session
Sourcepub fn new(initial_diagram: Diagram) -> Session
pub fn new(initial_diagram: Diagram) -> Session
A session over diagram, holding it and no history behind it.
The diagram is taken by value, so the caller has handed it over rather than lent it, and a copy the caller kept is a diagram of its own that this session never sees. Where it came from is not asked and not refused: a diagram is a value and any of them can be opened, which §8 of the model lists as deliberately unresolved rather than as missing.
Sourcepub fn diagram(&self) -> &Diagram
pub fn diagram(&self) -> &Diagram
The diagram this session holds, borrowed.
The step behind the position, and the diagram this session was created over when nothing is
behind it — read rather than rebuilt, and whole. Nothing is copied and nothing can be
changed through it, which is what makes it the answer for a caller that repaints on every
event. A caller that wants to keep one copies it — Diagram is Clone — and the copy is a
diagram like any other: changeable, free of the session and unconnected to it.
Sourcepub fn can_undo(&self) -> bool
pub fn can_undo(&self) -> bool
Whether there is a step behind the position, and so whether undo would
change anything.
Answerable without carrying it out, which is what lets a caller grey out a control rather than guess — the same answer the diagram gives a reference to a figure that is not there.
Sourcepub fn can_redo(&self) -> bool
pub fn can_redo(&self) -> bool
Whether there is a step ahead of the position, and so whether redo
would change anything.
Sourcepub fn affected_by_undo(&self) -> Option<ShapeId>
pub fn affected_by_undo(&self) -> Option<ShapeId>
The figure the step behind the position touched, or None when there is nothing behind.
Answerable without walking, like can_undo, which is why the walk
keeps its signature (§4 of the application model asks before it gives the step back). A
caller that has to know where to leave the selection asks this first and keeps the answer
across the one call that moves the position.
The answer is the step’s and not the command’s. For every command that names a figure it
is that id, and for Command::Add it is the identity the diagram assigned — the one case
the command alone cannot name, which is why the step records it (§5 of the editing model).
Sourcepub fn affected_by_redo(&self) -> Option<ShapeId>
pub fn affected_by_redo(&self) -> Option<ShapeId>
The figure the step ahead of the position touched, or None when there is nothing ahead.
The other direction of affected_by_undo, asked the same
way and recorded the same way.
Sourcepub fn undo(&self) -> Self
pub fn undo(&self) -> Self
Gives the step behind the position back: what the session this returns holds is one step earlier than what this one holds.
Nothing here is changed, and the same is true of redo and
perform. What comes back is a session standing one place earlier
over the same steps: a new position rather than a new history, every step a node this one
was already holding, and no diagram copied to produce it. At the oldest position there is
nothing behind, and what comes back is this session unchanged — no error, no report and no
panic, which is the answer the diagram gives a figure it does not hold and the reason
can_undo is asked rather than a result waited for.
Sourcepub fn redo(&self) -> Self
pub fn redo(&self) -> Self
Carries the step ahead of the position out again: the diagram it holds is what the session this returns holds again.
This session is not changed, for the reason undo gives. At the
newest position there is nothing ahead, and what comes back is this session unchanged — the
same silence in the other direction, and asked about by
can_redo rather than waited for.
Sourcepub fn perform(&self, command: Command) -> Self
pub fn perform(&self, command: Command) -> Self
Carries command out against the diagram this session holds, and hands back the session
standing over what it left: records the diagram the command leaves as a step, and discards
whatever was ahead of the position.
This session is not changed, and discarding what comes back changes nothing. That is
what keeping a session out of reach of a &mut buys, and it is why this is the one method
here whose result has to be kept rather than merely read.
The step is the diagram the command left, and that order is what fixes what the field holds. The state the first command found is the one state no step holds, and it is the field — which is why nothing writes the field again, and why the diagram in hand is either that field or a step and never a third thing. The other order would work too, and it would make the field hold the newest state rather than the first: a field whose contents change with every command, and a newest state kept outside the history, which is what makes the answer at the end of the history a case of its own instead of a step like any other.
The diagram in hand is the one the step is handed, so no diagram is copied to record it.
apply_to builds the state and hands it back, and that value is both what the step holds and
what the next call to diagram reads — a whole diagram on the write
path, which is what the other order would have cost, since a step written before the command
it belongs to has to be handed a copy of what that command went on to replace.
What the command becomes is carried out here and nowhere else. The mapping from a
meaning to one of the diagram’s seven operations is the one in Command, and the session
is what carries it out because only what was asked knows whether what it was asked to do
was one gesture or several: a figure dragged across a screen is one thing to the person
dragging it and many displacements to the diagram. Nothing reads that answer yet — §8 of
the model says what is left to write — and the field it will be read from is the step’s
command, which is recorded beside the diagram from this first version.
Nothing is inverted, and the steps are what make that unnecessary. Diagram::remove
freezes every reference naming the figure it takes and states that nothing re-hangs them,
so an inverse of that command cannot be derived — it could only be reconstructed from what
the caller happened to capture beforehand. Every state the history passes through is kept
instead, and giving a change back is one of them becoming the diagram again.
What was ahead of the position is discarded, which is what makes the history a line
rather than a tree. Branching earns its keep when a state somebody discarded is worth
returning to, and nothing here says one is: a caller that moves on from a state it gave back
has said the state was not worth keeping. What would reopen it is the first caller that
wants one back, and §6 of the model says so. It is kept_first rather than a
Vec::truncate, because the sequence holding the steps is not one that can be truncated
in place; the walk is where the cost of that choice is written down rather than paid
silently here.
A command that changed nothing records no step (#285). The diagram’s operations cannot fail, so a command naming a figure it does not hold changes nothing, and neither does one whose change gives the figure back as it was — a box at its smallest shrunk again, a line at its shortest shrunk again, a connector resized, a figure brought forward or to the front when it is already at the front, or backward or to the back when it is already at the back. What comes back is this session: the same steps, the same position and the same diagram, so what was ahead of the position stays ahead and a no-op discards no branch. The command knows which figure it touched and answers whether that figure changed; nothing here compares two diagrams, which a diagram has no way of doing.
Sourcepub fn add(&self, shape: Shape) -> (Self, ShapeId)
pub fn add(&self, shape: Shape) -> (Self, ShapeId)
Adds shape to the diagram this session holds, assigns it a ShapeId, records the
resulting diagram as a step, and hands back the new session beside the assigned identity.
This session is not changed, for the reason every method here gives: a new session is returned holding the step ahead and the original is left intact.
The step records Command::Add { shape } and the diagram add produced, so
undo gives the diagram back without the added figure and
redo restores it.
What was ahead of the position is discarded, exactly as in
perform: a branch is cut when a new figure is added from behind the
end of the history.