Skip to main content

geop_core_editor/editor/
mod.rs

1//! [`Editor`]: the whole of editing a program, as one state machine an
2//! editor drives with [`Command`]s and draws from the [`Update`]s it
3//! answers.
4//!
5//! It holds the program, its undo and redo, how far it runs, and the step
6//! being edited, and it builds whatever is shown — so an editor keeps no
7//! state of its own beyond the camera, renders what it is sent, and sends
8//! what the user did.
9//!
10//! What it offers is a [`Registry`]'s: the operations a step can be, how a
11//! part is drawn and listed, the exporters and inspectors, the panels, the
12//! tools and what settles the program's state after an edit. None of it is
13//! named here: an editor of a registry of three operations, with no panel
14//! and no settler, edits programs of those three.
15//!
16//! The program is the one of a file, among others it may place parts from
17//! (see [`geop_core_program::workspace`]): the editor is told the file's path
18//! with the program ([`Command::Load`]), and is sent the other files'
19//! programs whenever they change ([`Command::Files`]) — by the front end,
20//! which owns the files, be they a VS Code workspace's or the browser's.
21//! Only the file it edits is its own: switching to another, it keeps the
22//! program it leaves as that file's. What each step shows and does is the
23//! operation's; see [`crate::step_editor::StepEditor`].
24
25mod build;
26mod context;
27mod kept;
28mod protocol;
29#[cfg(test)]
30mod tests;
31
32pub use context::{Changed, EditorContext, Rerun};
33pub use protocol::{
34    Command, File, PlacedSteps, ProgramState, Report, ReportedStep, SceneState, StepInfo,
35    StepState, Update,
36};
37
38use geop_core_program::{
39    cache::Cache,
40    element::PickedElement,
41    operation_registry::OperationRegistry,
42    part::Part,
43    placed::{INSTANCE_SEPARATOR, Located, OUTSIDE, PlacedPart},
44    program::{Program, ProgramStep},
45    reader::{Reader, Seen},
46    runner::{Counts, Runner},
47    values::StateValues,
48    workspace::{FileStoreMut, Files, Location, Workspace},
49};
50use std::{
51    cell::RefCell,
52    collections::{BTreeMap, BTreeSet, HashMap},
53    rc::Rc,
54};
55
56use crate::keys::APP_KEYS;
57use crate::{
58    editable::Against,
59    editing::{Extent, Presentation, Shape, StepEditEvent, Style},
60    output::{Content, Export, Inspection, Output},
61    registry::{Drawings, Registry, StructureItem},
62    step_editor::{Editing, StepEditor},
63    tools::Tool,
64    view::{
65        EMPTY_EXTENT, PartView, ViewInstance, Visible, drawn,
66        lit::{Lighting, lighting},
67    },
68};
69use build::Build;
70use geop_core_math::{
71    geop_error::{GeopError, GeopResult},
72    primitives::{CoordinateSystem, Pose},
73    scalars::Scalar,
74    vector::Vector3,
75};
76use kept::Kept;
77
78/// How many files' builds an editor keeps (see `Editor::builds`): each
79/// holds every step's part, so a few large files are as many as fit.
80const KEPT_BUILDS: usize = 6;
81
82/// A part drawn, by what it was made of (see [`Reader::seen`]).
83type Drawing = (Seen, Rc<PartView>);
84
85/// How many parts drawn an editor keeps whole (see `Editor::views`): the
86/// part before the step edited, the part with it, and those of the last
87/// steps opened.
88const KEPT_VIEWS: usize = 4;
89
90/// The files an editor reads the programs it places parts from, and
91/// writes the files it is sent to: whatever its front end keeps, with
92/// whatever library its maker put beside them.
93type EditorFiles = Files<Box<dyn FileStoreMut>>;
94
95/// A step being edited, as one edit left it: its arguments, and the
96/// program's state.
97type Edited = (serde_json::Value, StateValues);
98
99/// The step being edited.
100struct Open {
101    /// Where it is, or goes.
102    index: usize,
103    /// Its id — for a new one, the id it gets once committed.
104    id: String,
105    /// Whether it is a new step, inserted at `index`, rather than the one
106    /// already there.
107    new: bool,
108    /// The program with the step in it, as its edits leave it: what the
109    /// step editor writes the step's arguments to — and its state, as the
110    /// step's edits and the solves after them leave it.
111    program: Program,
112    editor: StepEditor,
113    /// What picks test against, as drawn: the part before it, or what it
114    /// builds (see [`crate::editable::OperationInfo::picks_built`]).
115    view: Rc<PartView>,
116    /// The step as it was before each edit made to it since it was opened,
117    /// with the program's state: what undo goes back to while it is edited.
118    undo: Vec<Edited>,
119    redo: Vec<Edited>,
120    /// The step as it was when the drag going on started: a drag is one
121    /// edit, however many events it takes.
122    dragging: Option<Edited>,
123    /// The step as it was when an undo of its cancel opened it again: a
124    /// cancel of it as it was then is that cancel again, which redo, not
125    /// undo, takes back (see `Command::Cancel`).
126    reopened: Option<Edited>,
127    /// For a step of a part placed in the program, where it is placed:
128    /// `program` is then that part's, and the program run is the file's.
129    within: Option<Within>,
130}
131
132/// A step of a part placed in the program — however deep — edited where it
133/// is placed: the program run is the file's, the placed part's program — the
134/// step's edits in it — put in the workspace in place of its file, so the
135/// part placed, and every part it is placed in, is built anew and nothing
136/// else is. Its edits are made in the placed part's own coordinates: what
137/// the pointer does is moved there, and what the step shows moved out to
138/// where the part is placed.
139struct Within {
140    /// The path of the instance it is placed as: `neck/head`.
141    instance: String,
142    /// Where its program is: its file, and the files placing it.
143    location: Location,
144    /// The file's text when the step was opened: what cancelling puts back.
145    text: String,
146    /// Its program's runner.
147    runner: Runner,
148    /// The part placed, as the last run built it.
149    part: Rc<Part>,
150    /// Where it is placed in the part drawn.
151    pose: Pose,
152}
153
154impl Open {
155    /// The step, as its edits leave it.
156    fn step(&self) -> GeopResult<&ProgramStep> {
157        self.program.step(&self.id)
158    }
159
160    /// The step, and the program's state, as they are now: what an undo
161    /// comes back to.
162    fn edited(&self) -> GeopResult<Edited> {
163        Ok((self.step()?.args.clone(), self.program.state.clone()))
164    }
165}
166
167/// A step of a placed part to edit where it is placed: one of its steps, by
168/// id, or a new one of the operation `kind`, after them.
169#[derive(Debug)]
170enum PlacedStep {
171    Existing(String),
172    New(String),
173}
174
175/// What an undo takes back: an edit of the program, or of the file of a
176/// part placed in it, committed where the part is placed — each as it was
177/// before — or the cancel of a step that was edited.
178enum Undone {
179    Program(Program),
180    File {
181        path: String,
182        program: Program,
183    },
184    /// A step cancelled after it was edited: undoing the cancel opens it
185    /// again as it was left, with its own edits to undo.
186    Cancelled(Box<Open>),
187}
188
189/// The tool in hand, the key it was taken in hand by, and whether it was
190/// taken — or is the registry's idle tool, in hand while none is (see
191/// [`Registry::idle_tool`]).
192struct InHand {
193    key: String,
194    tool: Box<dyn Tool>,
195    taken: bool,
196}
197
198/// Which part is drawn, and without what — or, for a step edited on a
199/// sheet of its own, no part, the viewer framing the sheet: when it is the
200/// same as last time, the scene is not sent again. The part is told by
201/// what it is made of (see [`Reader::seen`]), not by when it was last
202/// built: the part before a sketch is the same however the sketch changes,
203/// and a whole assembly is not sent again for every line drawn on it.
204#[derive(Clone, PartialEq)]
205struct Shown {
206    seen: Seen,
207    steps: usize,
208    hidden: Vec<String>,
209    /// The colours placed parts are drawn in, by name (see
210    /// [`Editor::colors`]).
211    colors: BTreeMap<String, String>,
212    /// The sheet's centre and size.
213    sheet: Option<([f64; 3], f64)>,
214}
215
216/// A placed part as the viewer was sent it: the key of the component it is
217/// drawn from, where it is, and the colour it is drawn in.
218#[derive(PartialEq)]
219struct Sent {
220    component: String,
221    frame: [f64; 12],
222    color: Option<String>,
223}
224
225/// Editing a program: see the module docs.
226pub struct Editor {
227    /// What it offers.
228    registry: Rc<Registry>,
229    /// Its operations, as every runner it makes runs them.
230    operations: Rc<OperationRegistry>,
231    file: Program,
232    /// The file the program is in, once told: until then, the files it
233    /// places are named relative to the workspace's top folder.
234    path: Option<String>,
235    /// The other program files, which it places parts from.
236    workspace: Rc<EditorFiles>,
237    undo: Vec<Undone>,
238    redo: Vec<Undone>,
239    marker: Option<usize>,
240    open: Option<Open>,
241    preview: bool,
242    build: Build,
243    /// The builds of the files edited before, by path, the latest last:
244    /// opened again, a file takes up its steps as they were built, rather
245    /// than building them all again. At most [`KEPT_BUILDS`].
246    builds: Vec<(String, Build)>,
247    /// Counts runs (see [`EditorContext::runs`]).
248    run: u64,
249    /// What each step that ran builds on (see [`crate::editing::Dialog::picked`]),
250    /// with the step it was read from: what a step picked is in its
251    /// arguments, so it is read anew only when the step changed — not when
252    /// a drag only moved the program's state.
253    references: Vec<(ProgramStep, Vec<PickedElement>)>,
254    shown: Option<Shown>,
255    /// The files the last command added, for the update to say.
256    added: Option<Vec<File>>,
257    /// The other files the last command changed, for the update to carry.
258    written: Vec<File>,
259    /// The file the last command wrote, for the update to carry.
260    exported: Option<Export>,
261    /// Whether the last command asked for a report ([`Command::Report`]).
262    reporting: bool,
263    /// The placed parts the viewer was sent, by name, as sent — `None`
264    /// when the viewer may have none (see [`SceneState`]).
265    placed: Option<HashMap<String, Sent>>,
266    /// What the viewer keeps of the scenes it was sent: the components and
267    /// the meshes of faces, by key.
268    kept: Kept,
269    /// The tool in hand, if one is.
270    tool: Option<InHand>,
271    /// The inspector the last command asked, and what it told it: to answer
272    /// once it has run.
273    asked: Option<(String, serde_json::Value, Vec<PickedElement>)>,
274    /// The part as last drawn: what a tool picks from.
275    drawn: Option<Rc<PartView>>,
276    /// The last parts drawn, by what they were made of (see
277    /// [`Reader::seen`]): a part shown again is the view it was — opening
278    /// a step and cancelling it shows the same part. At most
279    /// [`KEPT_VIEWS`], the last drawn last.
280    views: RefCell<Vec<Drawing>>,
281    /// What each drawer drew of the parts drawn, by what it read: a part
282    /// drawn with other sketches than before — the part before a sketch
283    /// opened — draws its sketches again, not its faces.
284    drawings: Drawings,
285    /// What [`Command::Light`] lights.
286    lights: Vec<PickedElement>,
287    /// What the viewer was last sent to draw lit (see [`Update::lit`]).
288    lit: Option<Lighting>,
289    /// The names hidden and the structure the viewer was last sent (see
290    /// [`SceneState::hidden`]): a scene sends them again only where they
291    /// changed, or where it is sent whole.
292    listing: Option<(Vec<String>, Vec<StructureItem>)>,
293    /// The steps of the parts placed, as listed, by file and build (see
294    /// [`Editor::placed_steps`]).
295    listed: RefCell<HashMap<(String, *const Part), ListedBuild>>,
296}
297
298/// A placed part's steps as listed, and the build and program they list:
299/// listed again only for another build or another program.
300struct ListedBuild {
301    part: Rc<Part>,
302    program: Rc<Program>,
303    steps: Vec<StepInfo>,
304}
305
306impl Editor {
307    /// An editor of what `registry` offers, editing a program of no steps,
308    /// the files it places parts from read from `files`, what they are made
309    /// into kept in memory.
310    pub fn new(registry: Registry, files: impl FileStoreMut + 'static) -> Self {
311        Self::with_cache(registry, files, Cache::new())
312    }
313
314    /// [`Editor::new`], what its files are made into kept in `cache`: a
315    /// host process's, kept on disk too.
316    pub fn with_cache(
317        registry: Registry,
318        files: impl FileStoreMut + 'static,
319        cache: Cache,
320    ) -> Self {
321        let operations = Rc::new(registry.operations());
322        let files: Box<dyn FileStoreMut> = Box::new(files);
323        let mut editor = Self {
324            workspace: Files::with_cache(files, operations.clone(), cache),
325            build: Build::new(&operations),
326            registry: Rc::new(registry),
327            operations,
328            file: Program::default(),
329            path: None,
330            undo: Vec::new(),
331            redo: Vec::new(),
332            marker: None,
333            open: None,
334            preview: true,
335            builds: Vec::new(),
336            run: 0,
337            references: Vec::new(),
338            shown: None,
339            added: None,
340            written: Vec::new(),
341            exported: None,
342            reporting: false,
343            placed: None,
344            kept: Kept::default(),
345            tool: None,
346            asked: None,
347            drawn: None,
348            views: RefCell::default(),
349            drawings: Drawings::default(),
350            listed: RefCell::default(),
351            lights: Vec::new(),
352            lit: None,
353            listing: None,
354        };
355        editor.put_down();
356        editor
357    }
358
359    /// Puts down the tool in hand: the registry's idle tool is in hand
360    /// again, a new one — what a press and a move in the view do while no
361    /// other tool is taken and no step is edited (see
362    /// [`Registry::idle_tool`]) — or none.
363    fn put_down(&mut self) {
364        self.tool = (self.registry.idle_in_hand()).map(|(key, tool)| InHand {
365            key,
366            tool,
367            taken: false,
368        });
369    }
370
371    pub fn program(&self) -> &Program {
372        &self.file
373    }
374
375    /// Forgets what the viewer was sent: the next update sends all of the
376    /// scene, as to a viewer that has nothing — one starting afresh, or one
377    /// that never saw the updates someone else asked for (see
378    /// [`crate::agent::Session::new`]).
379    pub fn forget_viewer(&mut self) {
380        self.shown = None;
381        self.placed = None;
382    }
383
384    /// What it offers.
385    pub fn registry(&self) -> &Registry {
386        &self.registry
387    }
388
389    /// The part the program builds, as far as it runs now.
390    pub fn part(&self) -> Reader<'_> {
391        self.build.part.current()
392    }
393
394    /// How much work its runs have done: what a test bounds an edit by.
395    pub fn counts(&self) -> Counts {
396        self.build.runner.counts()
397    }
398
399    /// The step being edited, with its arguments as they now are.
400    pub fn editing(&self) -> Option<&ProgramStep> {
401        self.open.as_ref().and_then(|open| open.step().ok())
402    }
403
404    /// The step editor of the step being edited: its operation's editing
405    /// state among what it keeps.
406    pub fn step_editor(&self) -> Option<&StepEditor> {
407        self.open.as_ref().map(|open| &open.editor)
408    }
409
410    /// The tool in hand, if it is a `T`.
411    pub fn tool<T: Tool>(&self) -> Option<&T> {
412        let tool: &dyn std::any::Any = self.tool.as_ref()?.tool.as_ref();
413        tool.downcast_ref()
414    }
415
416    /// [`Editor::handle`] on the wire: `command` is a [`Command`] as JSON,
417    /// and the answer an [`Update`] as JSON. Shared by every front end that
418    /// is not Rust (the browser's wasm module, the VS Code host process),
419    /// so they all speak the same protocol. `Err` is a message that is not
420    /// itself an update: the command could not be read, or the update not
421    /// written.
422    pub fn handle_json(&mut self, command: &str) -> Result<String, String> {
423        let command: Command =
424            serde_json::from_str(command).map_err(|e| format!("reading command: {e}"))?;
425        serde_json::to_string(&self.handle(command)).map_err(|e| format!("writing update: {e}"))
426    }
427
428    /// The path of the file edited in the workspace: its top, until told.
429    fn own_file(&self) -> String {
430        let path = self.path.as_deref().unwrap_or_default();
431        geop_core_program::workspace::resolve("", path)
432    }
433
434    /// Tells the runner the workspace the program reads from, and the file
435    /// it is in there.
436    fn give(&mut self) {
437        self.build.runner.workspace = Some(self.workspace.clone());
438        self.build.runner.location = Location {
439            file: self.own_file(),
440            within: Vec::new(),
441        };
442    }
443
444    /// Runs the first `end` of the steps run now — all of them, if `None` —
445    /// with their state (see [`Self::current`]), keeping how far it ran and
446    /// why steps failed.
447    fn run_to(&mut self, end: Option<usize>) {
448        self.run_staged(end, self.preview);
449    }
450
451    /// [`Self::run_to`], a step of a placed part edited put in its file
452    /// first — as edited, if `edited`, else as it was — and the part placed
453    /// taken as built. As edited, the file ends at the step, as a step of
454    /// the program's own is edited: the steps after it are built once the
455    /// edit is done (`whole`).
456    fn run_staged(&mut self, end: Option<usize>, edited: bool) {
457        self.run_placed(end, edited, false);
458    }
459
460    /// [`Self::run_staged`], a placed part's file — as edited — whole, if
461    /// `whole`: what a commit builds.
462    fn run_placed(&mut self, end: Option<usize>, edited: bool, whole: bool) {
463        self.give();
464        if let Some(open) = &self.open
465            && let Some(within) = &open.within
466        {
467            let mut program = open.program.clone();
468            if !whole && !self.picks_whole(open) {
469                program.steps.truncate(open.index + 1);
470            }
471            let text = match edited {
472                true => program.to_json().ok(),
473                false => None,
474            };
475            let text = text.unwrap_or_else(|| within.text.clone());
476            self.workspace.write(&within.location.file, Some(text));
477        }
478        let program = match &self.open {
479            Some(open) if open.within.is_none() => &open.program,
480            _ => &self.file,
481        };
482        self.build.run(program, end);
483        if let Some(open) = &mut self.open
484            && let Some(within) = &mut open.within
485            && let Some(placed) = self.build.part.placed_at_path(&within.instance)
486        {
487            within.part = placed.part.clone();
488        }
489    }
490
491    /// The steps run now, and the state they run with: the step being
492    /// edited's, or the program's — for a step of a placed part, the
493    /// program's.
494    fn current(&self) -> &Program {
495        match &self.open {
496            Some(open) if open.within.is_none() => &open.program,
497            _ => &self.file,
498        }
499    }
500
501    /// The runner and the part the step `open` is edited with: the
502    /// program's, or, for a step of a placed part, that part's, placed in
503    /// the program's part where it is (see [`Against`]).
504    fn edited_with<'s>(&'s self, open: &'s Open) -> (&'s Runner, Against<'s>) {
505        match &open.within {
506            Some(within) => (&within.runner, self.placed_against(within)),
507            None => (&self.build.runner, Against::from(&self.build.part)),
508        }
509    }
510
511    /// The part placed as `within` describes, placed in the program's part
512    /// where it is: what a step of it reads there in context.
513    fn placed_against<'s>(&'s self, within: &'s Within) -> Against<'s> {
514        Against {
515            part: &within.part,
516            outside: self.build.part.placing(&within.instance),
517        }
518    }
519
520    /// Why the step `open` does not build, if it does not.
521    fn step_error(&self, open: &Open) -> Option<GeopError> {
522        match &open.within {
523            Some(within) => (within.part.failures().iter())
524                .find(|(step, _)| *step == open.id)
525                .map(|(_, e)| e.clone()),
526            None => self.build.step_error(open.index, &open.id),
527        }
528    }
529
530    /// Applies `command`, and says what to show now.
531    pub fn handle(&mut self, command: Command) -> Update {
532        // What undo goes back to — not taken for what is sent as often as
533        // the pointer moves, and cannot change the program; a key can, by
534        // committing the step. Another file's program is not something to
535        // undo back to.
536        let keeps_program = match &command {
537            Command::Event { event } => !matches!(event, StepEditEvent::Key { .. }),
538            Command::Preview { .. } => true,
539            _ => false,
540        };
541        let before = (!keeps_program).then(|| (self.file.clone(), self.path.clone()));
542        let result = self.apply(command);
543        if matches!(result, Ok(Changed::Program | Changed::Display))
544            && let Some((before, path)) = before
545            && path == self.path
546            && self.file != before
547        {
548            self.undo.push(Undone::Program(before));
549            self.redo.clear();
550        }
551        if !matches!(result, Ok(Changed::Nothing | Changed::Display)) {
552            // Settling runs the whole program, so what is shown runs after.
553            self.settle();
554            self.rerun();
555        }
556        if matches!(result, Ok(Changed::Program | Changed::Step)) {
557            self.after_edit();
558        }
559        let step = self.step_state();
560        let steps = self.shown_steps(step.as_ref());
561        let mut hidden = self.hidden(step.as_ref().map(|s| &s.presentation.pickable[..]), steps);
562        // What the step being edited asks to hide of what it builds.
563        if let Some(step) = &step {
564            hidden.extend(step.presentation.hidden.iter().cloned());
565        }
566        // Sorted: what a pick is made in looks names up in it (see
567        // [`Visible`]).
568        hidden.sort();
569        hidden.dedup();
570        let shown = Shown {
571            seen: self.shown_part(steps).seen(),
572            steps,
573            hidden,
574            colors: self.file.display.colors.clone(),
575            sheet: (step.as_ref())
576                .and_then(|s| s.presentation.sheet)
577                .map(|e| (e.center.to_array().map(|c| c.to_f64()), e.size.to_f64())),
578        };
579        let scene = (self.shown.as_ref() != Some(&shown)).then(|| self.scene(shown));
580        let mut error = result.as_ref().err().map(|e| e.to_string());
581        let inspection = match self.answer(step.is_none()) {
582            Ok(inspection) => inspection,
583            Err(e) => {
584                error = Some(e.to_string());
585                None
586            }
587        };
588        let program = match matches!(
589            result,
590            Ok(Changed::Program | Changed::Run | Changed::Display)
591        ) {
592            true => match self.program_state() {
593                Ok(program) => Some(program),
594                Err(e) => {
595                    error = Some(e.to_string());
596                    None
597                }
598            },
599            false => None,
600        };
601        // What an edit built is kept once it is done — not while a step
602        // being edited previews it, each event anew.
603        if self.open.is_none()
604            && let Some(cache) = self.workspace.cache()
605        {
606            cache.flush();
607        }
608        let report = std::mem::take(&mut self.reporting).then(|| self.report(step.as_ref()));
609        let tool = self.tool_presentation(step.is_none());
610        let lit = self.lit(
611            step.as_ref().map(|s| &s.presentation).or(tool.as_ref()),
612            &scene,
613        );
614        Update {
615            error,
616            program,
617            scene,
618            tool,
619            lit,
620            step,
621            files: self.added.take(),
622            written: (!self.written.is_empty()).then(|| std::mem::take(&mut self.written)),
623            inspection,
624            export: self.exported.take(),
625            report,
626        }
627    }
628
629    /// What a bug report attaches (see [`Report`]): the program, and the
630    /// step being edited as `step` shows it.
631    fn report(&self, step: Option<&StepState>) -> Report {
632        let reported = match (&self.open, step) {
633            (Some(open), Some(state)) => open.step().ok().map(|edited| ReportedStep {
634                id: state.id.clone(),
635                index: open.index,
636                placed: open.within.as_ref().map(|w| w.instance.clone()),
637                step: edited.clone(),
638                missing: state.missing.clone(),
639                error: state.error.clone(),
640                concerning: (state.presentation.dialog.0.iter())
641                    .find(|field| field.failure.is_some())
642                    .map(|field| field.key.clone()),
643            }),
644            _ => None,
645        };
646        Report {
647            program: self.file.clone(),
648            step: reported,
649            version: env!("CARGO_PKG_VERSION"),
650        }
651    }
652
653    /// What to draw lit now, as `presentation` — the step's, or the tool's —
654    /// and [`Command::Light`] light it: if it is not what the viewer was
655    /// last sent, or the viewer was sent a `scene` afresh.
656    fn lit(
657        &mut self,
658        presentation: Option<&Presentation>,
659        scene: &Option<SceneState>,
660    ) -> Option<Lighting> {
661        let part = self.drawn.as_ref()?;
662        let mut elements = presentation.map_or_else(Vec::new, |p| p.highlights.clone());
663        elements.extend(self.lights.iter().cloned());
664        let whole: BTreeSet<&str> = (presentation.iter())
665            .flat_map(|p| &p.visuals)
666            .filter(|v| matches!(v.style, Style::Hover | Style::Selected))
667            .filter_map(|v| match &v.shape {
668                Shape::Instance { name } => Some(name.as_str()),
669                _ => None,
670            })
671            .collect();
672        let lit = lighting(part, &elements, &whole);
673        let afresh = scene.as_ref().is_some_and(|s| s.all);
674        if !afresh && self.lit.as_ref() == Some(&lit) {
675            return None;
676        }
677        self.lit = Some(lit.clone());
678        Some(lit)
679    }
680
681    /// What to draw, `shown`: the part, and of the parts placed in it what
682    /// the viewer was not sent yet.
683    fn scene(&mut self, shown: Shown) -> SceneState {
684        self.shown = Some(shown.clone());
685        let part = match shown.sheet {
686            Some((center, size)) => Rc::new(PartView::blank(Extent {
687                center: Vector3::from_array(center.map(Scalar::from_f64)),
688                size: Scalar::from_f64(size),
689            })),
690            None => self.view_of(shown.steps),
691        };
692        let before = self.placed.take();
693        let all = before.is_none();
694        let before = before.unwrap_or_default();
695        if all {
696            self.kept.clear();
697        }
698        let sent = self.kept.send(&part);
699        let mut placed = HashMap::new();
700        let mut instances = Vec::new();
701        for instance in &part.instances {
702            let f = &instance.frame;
703            let frame = [f.origin(), f.u(), f.v(), f.w()].map(|p| p.to_array().map(|c| c.to_f64()));
704            let mut numbers = [0.0; 12];
705            for (number, x) in numbers.iter_mut().zip(frame.iter().flatten()) {
706                *number = *x;
707            }
708            let color = shown.colors.get(&instance.name).cloned();
709            let drawn = Sent {
710                component: instance.component.clone(),
711                frame: numbers,
712                color: color.clone(),
713            };
714            if before.get(&instance.name) != Some(&drawn) {
715                instances.push(ViewInstance {
716                    color,
717                    ..instance.clone()
718                });
719            }
720            placed.insert(instance.name.clone(), drawn);
721        }
722        let mut removed: Vec<String> = (before.keys())
723            .filter(|name| !placed.contains_key(*name))
724            .cloned()
725            .collect();
726        removed.sort();
727        self.placed = Some(placed);
728        self.drawn = Some(part.clone());
729        let structure = (self.registry)
730            .structure(&self.shown_part(shown.steps), &shown.hidden)
731            .unwrap_or_default();
732        // What the viewer has already is not sent again.
733        let listing = (shown.hidden, structure);
734        let (hidden, structure) = match !all && self.listing.as_ref() == Some(&listing) {
735            true => (None, None),
736            false => {
737                self.listing = Some(listing.clone());
738                (Some(listing.0), Some(listing.1))
739            }
740        };
741        SceneState {
742            part,
743            structure,
744            hidden,
745            instances,
746            all,
747            removed,
748            components: sent.components,
749            forgotten: sent.forgotten,
750            meshes: sent.meshes,
751            forgotten_meshes: sent.forgotten_meshes,
752        }
753    }
754
755    /// `ask` answered of the outputs offered and what was built, as an
756    /// exporter or inspector is given it, dated `date`.
757    fn ask<T>(
758        &mut self,
759        date: &str,
760        ask: impl FnOnce(&crate::output::Outputs, &mut Output) -> GeopResult<T>,
761    ) -> GeopResult<T> {
762        let editing = (self.open.as_ref())
763            .filter(|open| open.within.is_none())
764            .map(|open| open.id.clone());
765        let file = match &self.open {
766            Some(open) if open.within.is_none() => &open.program,
767            _ => &self.file,
768        };
769        let mut output = Output {
770            runner: &self.build.runner,
771            part: &mut self.build.part,
772            file,
773            editing: editing.as_deref(),
774            date,
775        };
776        ask(self.registry.outputs(), &mut output)
777    }
778
779    /// The answer to the inspector the last command asked, if it asked one
780    /// — else, `idle` (no step edited), the tool in hand's (see
781    /// [`Tool::inspection`]).
782    fn answer(&mut self, idle: bool) -> GeopResult<Option<Inspection>> {
783        if let Some((kind, options, picked)) = self.asked.take() {
784            return self.ask("", |outputs, output| {
785                let inspector = outputs.get_inspector(&kind)?;
786                inspector.inspect(output, &picked, &options).map(Some)
787            });
788        }
789        if !idle {
790            return Ok(None);
791        }
792        let Some(mut in_hand) = self.tool.take() else {
793            return Ok(None);
794        };
795        let inspection = in_hand.tool.inspection(&mut EditorContext::new(self));
796        self.tool = Some(in_hand);
797        inspection
798    }
799
800    /// What the tool in hand shows, if one is and no step is edited.
801    fn tool_presentation(&self, idle: bool) -> Option<Presentation> {
802        let in_hand = self.tool.as_ref().filter(|_| idle)?;
803        Some(in_hand.tool.presentation())
804    }
805
806    /// Opens the step `id` of `steps`, at `index` — `new`, inserted there,
807    /// or one already there — to edit.
808    fn open(&mut self, program: Program, index: usize, id: String, new: bool) -> GeopResult<()> {
809        // It runs once before it is edited: an editor edits a step against
810        // the part before it, as the last run left it — and with what it
811        // builds, if it is previewed.
812        self.give();
813        let whole = self.step_picks_whole(program.step(&id)?);
814        self.build.run(&program, self.runs_to(whole, index));
815        let Build { runner, part, .. } = &self.build;
816        let editor = StepEditor::new(self.registry.editors(), runner, part, &program, &id, new)?;
817        let picks_in = match whole {
818            true => self.build.part.current(),
819            false => self.build.view_at(&program, index),
820        };
821        let view = self.draw(&picks_in, &self.build.runner)?;
822        self.open = Some(Open {
823            index,
824            id,
825            new,
826            program,
827            editor,
828            view,
829            undo: Vec::new(),
830            redo: Vec::new(),
831            dragging: None,
832            reopened: None,
833            within: None,
834        });
835        Ok(())
836    }
837
838    /// Opens the step `id` of the part placed at `instance` in the program
839    /// — `neck`, or `neck/head` for one placed in a placed part, however
840    /// deep — to edit where it is placed (see [`Within`]).
841    /// `step` — one of its steps, by id, or a new one of an operation,
842    /// after them.
843    fn open_placed(&mut self, instance: String, step: PlacedStep) -> GeopResult<()> {
844        let said = format!("open_placed(instance={instance}, {step:?})");
845        let ctx = |e: GeopError| e.with_context(said.clone());
846        self.run_to(self.marker);
847        let (part, location) = (self.build.part.placed_at_path(&instance))
848            .map(|placed| (placed.part.clone(), placed.location.clone()))
849            .ok_or_else(|| GeopError::new(format!("no part is placed as `{instance}`")))
850            .map_err(ctx)?;
851        let mut program = (*self.workspace.program(&location.file).map_err(ctx)?).clone();
852        let text = self.workspace.files().read(&location.file).map_err(ctx)?;
853        let current = self.build.part.current();
854        let pose = (self.draw(&current, &self.build.runner)?.instances.iter())
855            .find(|placed| placed.name == instance)
856            .map(|placed| placed.pose)
857            .ok_or_else(|| GeopError::new(format!("the part placed as `{instance}` is not drawn")))
858            .map_err(ctx)?;
859        let runner = self.build.runner.nested(location.clone());
860        let against = Against {
861            part: &part,
862            outside: self.build.part.placing(&instance),
863        };
864        let (id, new) = match step {
865            PlacedStep::Existing(id) => (id, false),
866            PlacedStep::New(kind) => {
867                let id = program.fresh_id(self.label_of(&kind)?);
868                let editors = self.registry.editors();
869                let step = editors.new_step(&runner, against.clone(), &program, &kind, &id, None);
870                program.steps.push(step.map_err(ctx)?);
871                (id, true)
872            }
873        };
874        let index = program.index_of(&id).map_err(ctx)?;
875        let editor = StepEditor::new(
876            self.registry.editors(),
877            &runner,
878            against.clone(),
879            &program,
880            &id,
881            new,
882        )
883        .map_err(ctx)?;
884        let picks_in = match self.step_picks_whole(program.step(&id)?) {
885            true => against.current(),
886            false => against.before(&program, &id)?,
887        };
888        let view = self.draw(&picks_in, &runner)?;
889        let view = self.beside(&instance, &pose, view);
890        self.open = Some(Open {
891            index,
892            id,
893            new,
894            program,
895            editor,
896            view,
897            undo: Vec::new(),
898            redo: Vec::new(),
899            dragging: None,
900            reopened: None,
901            within: Some(Within {
902                instance,
903                location,
904                text,
905                runner,
906                part,
907                pose,
908            }),
909        });
910        Ok(())
911    }
912
913    /// Edits the file by `edit`. The marker stays at its step, found again
914    /// by id — or, if the edit took that step out, where it was: one rule
915    /// for where the program runs to, whatever an edit does to its steps.
916    fn edit_file<T>(&mut self, edit: impl FnOnce(&mut Program) -> GeopResult<T>) -> GeopResult<T> {
917        let marked = (self.marker)
918            .and_then(|m| self.file.steps.get(m))
919            .map(|step| step.id.clone());
920        let done = edit(&mut self.file)?;
921        if let (Some(marker), Some(marked)) = (self.marker, marked) {
922            let steps = &self.file.steps;
923            let at = steps.iter().position(|step| step.id == marked);
924            self.marker = Some(at.unwrap_or(marker)).filter(|&m| m < steps.len());
925        }
926        Ok(done)
927    }
928
929    /// Refuses while a step is edited.
930    fn idle(&self) -> GeopResult<()> {
931        match self.open {
932            Some(_) => Err(GeopError::new("finish editing the step first")),
933            None => Ok(()),
934        }
935    }
936
937    /// Applies `command`, saying what it changed.
938    fn apply(&mut self, command: Command) -> GeopResult<Changed> {
939        Ok(match command {
940            Command::Show => {
941                // All of it: the viewer may have started afresh.
942                self.forget_viewer();
943                Changed::Run
944            }
945            Command::New { kind } => {
946                self.idle()?;
947                self.put_down();
948                let index = self.marker.unwrap_or(self.file.steps.len());
949                self.run_to(Some(index));
950                let label = self.label_of(&kind)?;
951                let id = self.file.fresh_id(label);
952                // The part as just run: the part before where the step goes.
953                let Build { runner, part, .. } = &self.build;
954                let step = (self.registry.editors())
955                    .new_step(runner, part, &self.file, &kind, &id, None)?;
956                let mut program = self.file.clone();
957                program.steps.insert(index, step);
958                self.open(program, index, id, true)?;
959                Changed::Run
960            }
961            Command::Open { id, placed: None } => {
962                self.idle()?;
963                self.put_down();
964                let index = self.file.index_of(&id)?;
965                self.open(self.file.clone(), index, id, false)?;
966                Changed::Run
967            }
968            Command::Open {
969                id,
970                placed: Some(instance),
971            } => {
972                self.idle()?;
973                self.put_down();
974                self.open_placed(instance, PlacedStep::Existing(id))?;
975                Changed::Run
976            }
977            Command::NewIn { kind, placed } => {
978                self.idle()?;
979                self.put_down();
980                self.open_placed(placed, PlacedStep::New(kind))?;
981                Changed::Run
982            }
983            Command::Event { event } => {
984                // With nothing in progress in the step, Enter commits it, and
985                // Escape cancels it — an edited one so that undo opens it
986                // again (see `Command::Cancel`): what every front end's keys
987                // do. A step of an operation kept on Escape (a sketch) is
988                // committed instead, once it has all it needs: Escape is
989                // pressed again and again to stop what is in progress, and
990                // the press after the last must not throw the drawing away.
991                if let Some(open) = &self.open
992                    && let StepEditEvent::Key { key } = &event
993                    && matches!(key.as_str(), "Enter" | "Escape")
994                {
995                    let (runner, part) = self.edited_with(open);
996                    let editors = self.registry.editors();
997                    let form = (open.editor).form(editors, runner, part, &open.program)?;
998                    if open.editor.at_rest(&form) {
999                        let kept = (open.step().ok())
1000                            .and_then(|step| editors.info(&step.operation))
1001                            .is_some_and(|listed| listed.info.kept_on_escape)
1002                            && form.dialog.missing().is_empty();
1003                        return match (key.as_str(), kept) {
1004                            ("Enter", _) | (_, true) => self.apply(Command::Commit),
1005                            _ => self.apply(Command::Cancel),
1006                        };
1007                    }
1008                }
1009                let Some(open) = &mut self.open else {
1010                    let Some(mut in_hand) = self.tool.take() else {
1011                        return Ok(Changed::Nothing);
1012                    };
1013                    let changed = in_hand.tool.handle(&mut EditorContext::new(self), &event);
1014                    self.tool = Some(in_hand);
1015                    return changed;
1016                };
1017                let before = open.edited()?;
1018                // A step of a placed part is edited in its own coordinates.
1019                let (event, runner, part) = match &open.within {
1020                    Some(within) => (
1021                        event.moved(&within.pose.inverse().motion())?,
1022                        &within.runner,
1023                        Against {
1024                            part: &within.part,
1025                            outside: self.build.part.placing(&within.instance),
1026                        },
1027                    ),
1028                    None => (event, &self.build.runner, Against::from(&self.build.part)),
1029                };
1030                // Picked in what the viewer was last told to draw.
1031                let scene = Visible {
1032                    view: &open.view,
1033                    hidden: (self.shown.as_ref()).map_or(&[], |shown| &shown.hidden),
1034                };
1035                let editing = Editing {
1036                    editors: self.registry.editors(),
1037                    runner,
1038                    part: part.clone(),
1039                    program: &mut open.program,
1040                    scene: &scene,
1041                };
1042                open.editor.handle(editing, &event)?;
1043                let after = open.edited()?;
1044                // What undo goes back to: the step before this edit — before
1045                // the whole drag, for one.
1046                let start = open.dragging.take().unwrap_or_else(|| before.clone());
1047                if matches!(event, StepEditEvent::Drag { done: false, .. }) {
1048                    open.dragging = Some(start);
1049                } else if start != after {
1050                    open.undo.push(start);
1051                    open.redo.clear();
1052                }
1053                // A drag changes neither, but what it pulls moves the parts
1054                // — of the program, or of the placed part the step is of.
1055                // The pointer moving over the view pulls nothing new: what
1056                // the step pulled was solved for at the edit that pulled it,
1057                // and a hover is not solved for again (nor its form built
1058                // a second time).
1059                let moved_only = matches!(
1060                    event,
1061                    StepEditEvent::Hover { .. } | StepEditEvent::Leave | StepEditEvent::View { .. }
1062                );
1063                let pulls = !moved_only
1064                    && !(open.editor)
1065                        .form(self.registry.editors(), runner, part, &open.program)?
1066                        .pulls
1067                        .is_empty();
1068                match before == after && !pulls {
1069                    true => Changed::Nothing,
1070                    false => Changed::Step,
1071                }
1072            }
1073            Command::Commit => {
1074                let Some(open) = &self.open else {
1075                    return Err(GeopError::new("no step is being edited"));
1076                };
1077                let dialog = self.presentation(open)?.dialog;
1078                let missing = dialog.missing();
1079                if !missing.is_empty() {
1080                    return Err(GeopError::new(format!(
1081                        "pick the {} first",
1082                        missing.join(" and ")
1083                    )));
1084                }
1085                // Not previewed, it was not built: it is now, once.
1086                let index = open.index;
1087                let placed = open.within.is_some();
1088                if !self.preview || placed {
1089                    match placed {
1090                        true => self.run_placed(self.marker, true, true),
1091                        false => self.run_to(Some(index + 1)),
1092                    }
1093                }
1094                let Some(open) = &self.open else {
1095                    return Err(GeopError::new("no step is being edited"));
1096                };
1097                if let Some(error) = self.step_error(open) {
1098                    return Err(error.with_context("the step does not build"));
1099                }
1100                let committed = open.program.clone();
1101                committed.validate(&self.build.runner.registry)?;
1102                // A step of a placed part is committed to that part's file,
1103                // which the front end is given to keep.
1104                if let Some(within) = &open.within {
1105                    let path = within.location.file.clone();
1106                    let before = Program::from_json(&within.text)?;
1107                    if before != committed {
1108                        self.undo.push(Undone::File {
1109                            path: path.clone(),
1110                            program: before,
1111                        });
1112                        self.redo.clear();
1113                    }
1114                    self.written.push(File {
1115                        path,
1116                        program: committed,
1117                    });
1118                    self.open = None;
1119                    return Ok(Changed::Run);
1120                }
1121                self.open = None;
1122                // How the program is shown is the file's: it may have
1123                // changed while the step was edited.
1124                self.edit_file(|file| {
1125                    *file = Program {
1126                        display: std::mem::take(&mut file.display),
1127                        ..committed
1128                    };
1129                    Ok(())
1130                })?;
1131                Changed::Program
1132            }
1133            Command::Cancel => {
1134                match self.open.take() {
1135                    // A placed part's file goes back to what it was.
1136                    Some(Open {
1137                        within: Some(within),
1138                        ..
1139                    }) => {
1140                        (self.workspace).write(&within.location.file, Some(within.text));
1141                    }
1142                    // What was edited is not lost: undo opens it again. A
1143                    // step opened again so and cancelled as it was is that
1144                    // cancel redone: undo goes on past it, redo opens it.
1145                    Some(open) if !open.undo.is_empty() => {
1146                        let again = open.reopened.is_some()
1147                            && open.reopened.as_ref() == open.edited().ok().as_ref();
1148                        match again {
1149                            true => self.redo.push(Undone::Cancelled(Box::new(open))),
1150                            false => {
1151                                self.undo.push(Undone::Cancelled(Box::new(open)));
1152                                self.redo.clear();
1153                            }
1154                        }
1155                    }
1156                    _ => {}
1157                }
1158                Changed::Run
1159            }
1160            // Off, the step edited is no longer built; on, it is built again.
1161            Command::Preview { preview } => {
1162                self.preview = preview;
1163                Changed::Run
1164            }
1165            Command::Panel { key, payload } => {
1166                let registry = self.registry.clone();
1167                let panel = registry.get_panel(&key)?;
1168                panel.command(&mut EditorContext::new(self), payload)?
1169            }
1170            Command::Remove { id } => {
1171                self.idle()?;
1172                self.edit_file(|file| {
1173                    let index = file.index_of(&id)?;
1174                    file.steps.remove(index);
1175                    Ok(())
1176                })?;
1177                Changed::Program
1178            }
1179            Command::Move { id, index } => {
1180                self.idle()?;
1181                self.edit_file(|file| {
1182                    let from = file.index_of(&id)?;
1183                    let steps = &mut file.steps;
1184                    if index >= steps.len() {
1185                        return Err(GeopError::new(format!(
1186                            "cannot move step {id:?} to {index}: the program has {} steps",
1187                            steps.len()
1188                        )));
1189                    }
1190                    let step = steps.remove(from);
1191                    steps.insert(index, step);
1192                    Ok(())
1193                })?;
1194                Changed::Program
1195            }
1196            Command::Copy { id } => {
1197                // The step as it is edited, if it is the one open — and the
1198                // file it is in, which the files it names are named from.
1199                let (program, from) = match &self.open {
1200                    Some(open) if open.id == id => (
1201                        &open.program,
1202                        open.within.as_ref().map(|w| w.location.file.clone()),
1203                    ),
1204                    _ => (&self.file, None),
1205                };
1206                let from = from.unwrap_or_else(|| self.own_file());
1207                let mut excerpt = program.excerpt(&id)?;
1208                // Named from the workspace's top, pasted in a file anywhere
1209                // they name the same files (see [`Command::Paste`]).
1210                let files = self.workspace.list();
1211                excerpt.rewrite_texts(|text| {
1212                    let path = self.workspace.resolve(&from, text);
1213                    (path != text && files.contains(&path)).then_some(path)
1214                });
1215                self.exported = Some(Export {
1216                    name: format!("{id}.geop"),
1217                    media_type: "application/json".into(),
1218                    content: Content::Text(excerpt.to_json()?),
1219                });
1220                Changed::Nothing
1221            }
1222            Command::Paste { text } => {
1223                self.idle()?;
1224                let mut pasted = Program::from_json(&text).map_err(|e| {
1225                    e.with_context("pasting: what is pasted is not steps copied from geop")
1226                })?;
1227                // The files it names, named from the workspace's top as a
1228                // copy names them, named from the file pasted in.
1229                let (own, files) = (self.own_file(), self.workspace.list());
1230                pasted.rewrite_texts(|text| {
1231                    let path = self.workspace.resolve("", text);
1232                    let named = self.workspace.relative(&own, &path);
1233                    (named != text && files.contains(&path)).then_some(named)
1234                });
1235                let index = self.marker.unwrap_or(self.file.steps.len());
1236                let registry = self.registry.clone();
1237                let editors = registry.editors();
1238                let label = |step: &ProgramStep| match editors.info(&step.operation) {
1239                    Some(listed) => listed.info.label.to_string(),
1240                    None => step.operation.clone(),
1241                };
1242                let operations = self.operations.clone();
1243                self.edit_file(|file| file.paste(pasted, index, &operations, label))?;
1244                Changed::Program
1245            }
1246            Command::Seek { marker } => {
1247                self.idle()?;
1248                self.marker = marker.filter(|&m| m < self.file.steps.len());
1249                Changed::Run
1250            }
1251            Command::Load { program, path } => {
1252                self.idle()?;
1253                program.validate(&self.build.runner.registry)?;
1254                if let Some(path) = path
1255                    && Some(&path) != self.path.as_ref()
1256                {
1257                    // Another file: the one left is as it was edited, which
1258                    // the new one may place; its history is not the new
1259                    // one's, and what the new one places resolves from
1260                    // elsewhere.
1261                    let build = match self.builds.iter().position(|(kept, _)| *kept == path) {
1262                        Some(k) => self.builds.remove(k).1,
1263                        None => Build::new(&self.operations),
1264                    };
1265                    let left_build = std::mem::replace(&mut self.build, build);
1266                    if let Some(left) = self.path.replace(path) {
1267                        self.workspace.write(&left, Some(self.file.to_json()?));
1268                        self.builds.push((left, left_build));
1269                        if self.builds.len() > KEPT_BUILDS {
1270                            self.builds.remove(0);
1271                        }
1272                    }
1273                    self.undo.clear();
1274                    self.redo.clear();
1275                }
1276                self.file = program;
1277                self.marker = None;
1278                Changed::Program
1279            }
1280            Command::Files { files } => {
1281                // Only what was built from a file that changed is built
1282                // again; a file saved as it was changes nothing.
1283                let mut changed = false;
1284                for (path, text) in files {
1285                    changed |= self.workspace.write(&path, text);
1286                }
1287                if !changed {
1288                    return Ok(Changed::Nothing);
1289                }
1290                // The library's files changed: every step that reads them
1291                // reads them again, and takes what it kept where nothing
1292                // changed.
1293                // What the step being edited is built on may have changed.
1294                if let Some(index) = self.open.as_ref().map(|open| open.index) {
1295                    let placed = (self.open.as_ref()).is_some_and(|open| open.within.is_some());
1296                    self.run_to(if placed { self.marker } else { Some(index) });
1297                    if let Some(open) = &self.open {
1298                        let view = self.edit_view(open)?;
1299                        if let Some(open) = &mut self.open {
1300                            open.view = view;
1301                        }
1302                    }
1303                }
1304                Changed::Run
1305            }
1306            Command::LoadExample { name } => {
1307                self.idle()?;
1308                self.file = self.registry.example(&name)?;
1309                self.marker = None;
1310                Changed::Program
1311            }
1312            Command::LoadWorkspaceExample { name, folder } => {
1313                self.idle()?;
1314                let files: Vec<File> = (self.registry.workspace_example(&name)?.into_iter())
1315                    .map(|(path, program)| File {
1316                        path: match &folder {
1317                            Some(folder) => format!("{folder}/{path}"),
1318                            None => path.to_string(),
1319                        },
1320                        program,
1321                    })
1322                    .collect();
1323                let first = files
1324                    .first()
1325                    .ok_or_else(|| GeopError::new(format!("the example {name:?} has no files")))?;
1326                // Its first file opened as any file is — the one left kept
1327                // as edited, and what was shown of it, and how, forgotten —
1328                // before its files are written over what was there.
1329                let (program, path) = (first.program.clone(), Some(first.path.clone()));
1330                self.apply(Command::Load { program, path })?;
1331                for file in &files {
1332                    self.workspace
1333                        .write(&file.path, Some(file.program.to_json()?));
1334                }
1335                self.added = Some(files);
1336                Changed::Program
1337            }
1338            Command::Visibility { name, visible } => {
1339                self.file.display.visibility.insert(name, visible);
1340                Changed::Display
1341            }
1342            Command::Appearance { name, color } => {
1343                match color {
1344                    Some(color) => {
1345                        let hex = color.strip_prefix('#').unwrap_or_default();
1346                        if hex.len() != 6 || !hex.chars().all(|c| c.is_ascii_hexdigit()) {
1347                            return Err(GeopError::new(format!(
1348                                "{color:?} is no colour: a colour is `#rrggbb`"
1349                            )));
1350                        }
1351                        self.file.display.colors.insert(name, color)
1352                    }
1353                    None => self.file.display.colors.remove(&name),
1354                };
1355                Changed::Display
1356            }
1357            Command::Isolate { names } => {
1358                self.file.display.isolated = names.into_iter().collect();
1359                Changed::Display
1360            }
1361            Command::Light { elements } => {
1362                self.lights = elements;
1363                Changed::Nothing
1364            }
1365            #[expect(
1366                clippy::panic,
1367                reason = "the command is a panic on purpose, to check front ends recover"
1368            )]
1369            Command::Crash => panic!("the kernel was asked to crash"),
1370            Command::Report => {
1371                self.reporting = true;
1372                Changed::Nothing
1373            }
1374            Command::Export {
1375                exporter,
1376                format,
1377                options,
1378                date,
1379            } => {
1380                let export = self.ask(&date, |outputs, output| {
1381                    let exporter = outputs.get_exporter(&exporter)?;
1382                    exporter.export(output, format.as_deref(), &options)
1383                })?;
1384                self.exported = Some(export);
1385                Changed::Nothing
1386            }
1387            Command::Tool { key } => {
1388                self.idle()?;
1389                match key {
1390                    Some(key) => {
1391                        self.tool = Some(InHand {
1392                            tool: self.registry.tool_in_hand(&key)?,
1393                            key,
1394                            taken: true,
1395                        })
1396                    }
1397                    None => self.put_down(),
1398                }
1399                Changed::Run
1400            }
1401            Command::Inspect {
1402                inspector,
1403                options,
1404                picked,
1405            } => {
1406                self.asked = Some((inspector, options, picked));
1407                Changed::Nothing
1408            }
1409            Command::Undo | Command::Redo if self.open.is_some() => {
1410                // While a step is edited, its own edits.
1411                let Some(open) = self.open.as_mut() else {
1412                    return Ok(Changed::Nothing);
1413                };
1414                let now = open.edited()?;
1415                let (from, to) = match command {
1416                    Command::Undo => (&mut open.undo, &mut open.redo),
1417                    _ => (&mut open.redo, &mut open.undo),
1418                };
1419                let Some((args, state)) = from.pop() else {
1420                    return Ok(Changed::Nothing);
1421                };
1422                to.push(now);
1423                open.program.step_mut(&open.id)?.args = args;
1424                open.program.state = state;
1425                let (runner, part) = match &open.within {
1426                    Some(within) => (
1427                        &within.runner,
1428                        Against {
1429                            part: &within.part,
1430                            outside: self.build.part.placing(&within.instance),
1431                        },
1432                    ),
1433                    None => (&self.build.runner, Against::from(&self.build.part)),
1434                };
1435                let editors = self.registry.editors();
1436                (open.editor).restart(editors, runner, part, &open.program)?;
1437                Changed::Step
1438            }
1439            Command::Undo | Command::Redo => {
1440                let (from, to) = match command {
1441                    Command::Undo => (&mut self.undo, &mut self.redo),
1442                    _ => (&mut self.redo, &mut self.undo),
1443                };
1444                let Some(undone) = from.pop() else {
1445                    return Ok(Changed::Nothing);
1446                };
1447                match undone {
1448                    Undone::Program(program) => {
1449                        to.push(Undone::Program(self.file.clone()));
1450                        self.edit_file(|file| {
1451                            *file = program;
1452                            Ok(())
1453                        })?;
1454                    }
1455                    // A placed part's file, written back as it was, for the
1456                    // front end to keep too.
1457                    Undone::File { path, program } => {
1458                        let now = (*self.workspace.program(&path)?).clone();
1459                        to.push(Undone::File {
1460                            path: path.clone(),
1461                            program: now,
1462                        });
1463                        self.workspace.write(&path, Some(program.to_json()?));
1464                        self.written.push(File { path, program });
1465                    }
1466                    // The cancel undone: the step is edited again, built as
1467                    // it is when opened.
1468                    Undone::Cancelled(mut open) => {
1469                        open.reopened = Some(open.edited()?);
1470                        self.put_down();
1471                        self.give();
1472                        (self.build)
1473                            .run(&open.program, Some(open.index + usize::from(self.preview)));
1474                        self.open = Some(*open);
1475                    }
1476                }
1477                // Undoing is not itself an edit to undo.
1478                Changed::Run
1479            }
1480        })
1481    }
1482
1483    /// Settles the program's state by the registry's settler (see
1484    /// [`crate::registry::Settle`]), and keeps where it put it: the
1485    /// program's state, which every step then sees. With no settler, the
1486    /// state is kept as it is given.
1487    ///
1488    /// While a step that pulls nothing ([`crate::editing::Pull`]) is edited,
1489    /// only the program up to it runs: the steps after it are built again
1490    /// once the edit is done, not on every move of the pointer — editing a
1491    /// sketch early in a long program would otherwise rebuild everything
1492    /// after it each time. What the step edited pulls, it pulls first;
1493    /// otherwise the settler moves the step's own state first (see
1494    /// [`crate::registry::Settle::settle`]). Once the whole program has
1495    /// run, state it no longer solves for goes.
1496    ///
1497    /// It may leave the runner having run the whole program, not what is
1498    /// shown: [`Self::rerun`] runs that.
1499    fn settle(&mut self) {
1500        let registry = self.registry.clone();
1501        let Some(settler) = registry.get_settler() else {
1502            return;
1503        };
1504        // A step of a placed part edited pulls that part's state: its
1505        // program's, which its file is written with, solved where the part
1506        // is built — then the program placing it runs with it.
1507        if self.preview
1508            && let Some(open) = &mut self.open
1509            && let Some(within) = &open.within
1510        {
1511            let editors = self.registry.editors();
1512            let against = Against {
1513                part: &within.part,
1514                outside: self.build.part.placing(&within.instance),
1515            };
1516            let pulls = (open
1517                .editor
1518                .form(editors, &within.runner, against, &open.program))
1519            .map(|form| form.pulls)
1520            .unwrap_or_default();
1521            if !pulls.is_empty() {
1522                let mut part = (*within.part).clone();
1523                let mut rerun = Rerun {
1524                    runner: &within.runner,
1525                    part: &mut part,
1526                    program: &open.program,
1527                };
1528                let moved = settler.settle(&mut rerun, &pulls, Some(&open.id));
1529                open.program.state.extend(moved.unwrap_or_default());
1530            }
1531        }
1532        // Up to the step edited first: what it pulls is known from the part
1533        // it is applied to.
1534        self.run_to(match &self.open {
1535            Some(open) if open.within.is_none() => self.runs_to(self.picks_whole(open), open.index),
1536            _ => None,
1537        });
1538        // A step not previewed is not built, and pulls nothing along; nor
1539        // does a step of a placed part, whose state is its own.
1540        let pulls = match &self.open {
1541            Some(open) if self.preview && open.within.is_none() => (open.editor.form(
1542                self.registry.editors(),
1543                &self.build.runner,
1544                &self.build.part,
1545                &open.program,
1546            ))
1547            .map(|form| form.pulls)
1548            .unwrap_or_default(),
1549            _ => Vec::new(),
1550        };
1551        // The whole program, when the state it solves for may move: with no
1552        // step edited, or while the step edited pulls, which carries what
1553        // is after it along. Otherwise — a sketch, a feature — the steps
1554        // after it are built again once the edit is done.
1555        let whole = self.open.is_none() || !pulls.is_empty();
1556        if whole && self.open.is_some() {
1557            self.run_to(None);
1558        }
1559        let program = self.current().clone();
1560        // Which state the program solves for is only known once all of it
1561        // has run: until then, none is dropped.
1562        let complete =
1563            whole && self.build.ran == program.steps.len() && self.build.errors.is_empty();
1564        let own = (self.open.as_ref()).map(|open| open.id.clone());
1565        let mut rerun = Rerun {
1566            runner: &self.build.runner,
1567            part: &mut self.build.part,
1568            program: &program,
1569        };
1570        let moved = (settler.settle(&mut rerun, &pulls, own.as_deref())).unwrap_or_default();
1571        let mut next = program.state.clone();
1572        next.extend(moved);
1573        if complete && let Ok(solvable) = settler.solvable(&self.part()) {
1574            next.retain(|name, _| solvable.contains(name));
1575        }
1576        if next == program.state {
1577            return;
1578        }
1579        match &mut self.open {
1580            Some(open) if open.within.is_none() => open.program.state = next,
1581            _ => self.file.state = next,
1582        }
1583    }
1584
1585    /// Tells every panel an edit was made (see
1586    /// [`crate::registry::Panel::after_edit`]): once an edit, never for
1587    /// every move of the pointer.
1588    fn after_edit(&mut self) {
1589        let registry = self.registry.clone();
1590        for panel in registry.panels() {
1591            panel.after_edit(&mut EditorContext::new(self));
1592        }
1593    }
1594
1595    /// How far the program runs while its step at `index` is edited: through
1596    /// it, if it is previewed — otherwise it is not built at all, however
1597    /// long it would take, until it is committed or previewed again. A step
1598    /// that picks in the `whole` part has as much built as the program shows.
1599    fn runs_to(&self, whole: bool, index: usize) -> Option<usize> {
1600        match whole {
1601            true => self.marker.map(|marker| marker.max(index + 1)),
1602            false => Some(index + usize::from(self.preview)),
1603        }
1604    }
1605
1606    /// Runs what is shown: the program up to and including the step being
1607    /// edited, if it is previewed, or as far as it runs.
1608    fn rerun(&mut self) {
1609        self.run_to(match &self.open {
1610            Some(open) if open.within.is_none() => self.runs_to(self.picks_whole(open), open.index),
1611            _ => self.marker,
1612        });
1613        self.run += 1;
1614        let steps = self.current().clone();
1615        let before = std::mem::take(&mut self.references);
1616        self.references = (steps.steps.iter().take(self.build.ran).enumerate())
1617            .map(|(i, step)| {
1618                if let Some((_, picked)) = before.get(i).filter(|(read, _)| read == step) {
1619                    return (step.clone(), picked.clone());
1620                }
1621                // What a step picks is its arguments': not what it built,
1622                // which is why it is kept for as long as the step is the
1623                // same, and read from the form a list shows.
1624                let Build { runner, part, .. } = &self.build;
1625                let picked = (self
1626                    .registry
1627                    .editors()
1628                    .listed(runner, part, &steps, &step.id))
1629                .map(|form| form.dialog.picked().cloned().collect())
1630                .unwrap_or_default();
1631                (step.clone(), picked)
1632            })
1633            .collect();
1634        if let Some(open) = &self.open
1635            && (self.picks_whole(open) || self.picks_built(open))
1636            && let Ok(view) = self.edit_view(open)
1637            && let Some(open) = &mut self.open
1638        {
1639            open.view = view;
1640        }
1641    }
1642
1643    /// What the step being edited shows.
1644    fn presentation(&self, open: &Open) -> GeopResult<Presentation> {
1645        let (runner, part) = self.edited_with(open);
1646        let editors = self.registry.editors();
1647        let scene = Visible {
1648            view: &open.view,
1649            hidden: (self.shown.as_ref()).map_or(&[], |shown| &shown.hidden),
1650        };
1651        let presentation =
1652            (open.editor).presentation(editors, runner, part, &open.program, &scene)?;
1653        match &open.within {
1654            Some(within) => presentation.placed(&within.instance, &within.pose.motion()),
1655            None => Ok(presentation),
1656        }
1657    }
1658
1659    /// What the operation `kind` is called.
1660    fn label_of(&self, kind: &str) -> GeopResult<&'static str> {
1661        (self.registry.editors().info(kind))
1662            .map(|listed| listed.info.label)
1663            .ok_or_else(|| GeopError::new(format!("unknown operation `{kind}`")))
1664    }
1665
1666    fn step_state(&self) -> Option<StepState> {
1667        let open = self.open.as_ref()?;
1668        let listed = self.registry.editors().info(&open.step().ok()?.operation)?;
1669        let info = listed.info;
1670        let mut presentation = self.presentation(open).ok()?;
1671        let missing: Vec<String> = (presentation.dialog.missing().into_iter())
1672            .map(str::to_string)
1673            .collect();
1674        // A step not previewed is not built, so whether it builds is not
1675        // known until it is committed.
1676        let error = (missing.is_empty() && self.preview)
1677            .then(|| self.step_error(open))
1678            .flatten();
1679        // Said beside the field whose value it fails with, too.
1680        if error.is_some()
1681            && open.within.is_none()
1682            && let Some((key, why)) = self.build.arguments.get(&open.id)
1683        {
1684            presentation.dialog.concerning(key, why);
1685        }
1686        Some(StepState {
1687            kind: listed.kind,
1688            label: info.label,
1689            doc: info.doc,
1690            id: (!open.new).then(|| open.id.clone()),
1691            error: error.map(|e| e.to_string()),
1692            missing,
1693            presentation,
1694            preview: self.preview,
1695            can_undo: !open.undo.is_empty(),
1696            can_redo: !open.redo.is_empty(),
1697        })
1698    }
1699
1700    /// How many steps the drawn part is built by: with the step being
1701    /// edited, if it is previewed, builds, and no plane is worked in —
1702    /// then what is drawn is drawn a second time, in the plane.
1703    fn shown_steps(&self, step: Option<&StepState>) -> usize {
1704        match (&self.open, step) {
1705            (Some(open), Some(step)) if open.within.is_none() && !self.picks_whole(open) => {
1706                let preview = self.preview
1707                    && step.missing.is_empty()
1708                    && step.error.is_none()
1709                    && step.presentation.focus.is_none();
1710                open.index + usize::from(preview)
1711            }
1712            _ => self.build.ran,
1713        }
1714    }
1715
1716    /// The part `steps` steps build, as it is shown to be.
1717    fn shown_part(&self, steps: usize) -> Reader<'_> {
1718        self.build.view_at(self.current(), steps)
1719    }
1720
1721    /// Whether the step `step` picks in the whole part, and is edited with
1722    /// it built (see [`crate::editable::OperationInfo::picks_whole`]).
1723    fn step_picks_whole(&self, step: &ProgramStep) -> bool {
1724        (self.registry.editors().info(&step.operation)).is_some_and(|l| l.info.picks_whole)
1725    }
1726
1727    /// Whether the step `open` picks in the whole part.
1728    fn picks_whole(&self, open: &Open) -> bool {
1729        open.step().is_ok_and(|step| self.step_picks_whole(step))
1730    }
1731
1732    /// Whether the step `open` picks from what it builds (see
1733    /// [`crate::editable::OperationInfo::picks_built`]), now that it does.
1734    fn picks_built(&self, open: &Open) -> bool {
1735        let picks_built = (open.step().ok())
1736            .and_then(|step| self.registry.editors().info(&step.operation))
1737            .is_some_and(|listed| listed.info.picks_built);
1738        picks_built && self.step_error(open).is_none()
1739    }
1740
1741    /// The part the step `open` picks from: the part before it — or, for
1742    /// one that picks from what it builds, that, once it builds, and for
1743    /// one that picks in the whole part, that.
1744    fn picks_in<'s>(&'s self, open: &'s Open) -> Reader<'s> {
1745        let built = self.picks_built(open);
1746        match &open.within {
1747            Some(within) if self.picks_whole(open) => self.placed_against(within).current(),
1748            None if self.picks_whole(open) => self.build.part.current(),
1749            Some(within) if built => self.placed_against(within).after(&open.id),
1750            Some(within) => {
1751                let against = self.placed_against(within);
1752                (against.before(&open.program, &open.id)).unwrap_or_else(|_| against.current())
1753            }
1754            None => (self.build).view_at(&open.program, open.index + usize::from(built)),
1755        }
1756    }
1757
1758    /// What the step `open` picks in, as drawn (see [`Self::picks_in`]):
1759    /// for a step of a placed part, with the parts placed beside it (see
1760    /// [`Self::beside`]).
1761    fn edit_view(&self, open: &Open) -> GeopResult<Rc<PartView>> {
1762        let view = self.draw(&self.picks_in(open), self.edited_with(open).0)?;
1763        Ok(match &open.within {
1764            Some(within) => self.beside(&within.instance, &within.pose, view),
1765            None => view,
1766        })
1767    }
1768
1769    /// `view`, of the part placed at `instance` at `pose`, with the parts
1770    /// placed beside it before it — at every level its path goes through:
1771    /// beside it in the part placing it, and beside that in the part placing
1772    /// that, however deep — each named as that part reads it in context,
1773    /// `../bracket`, `../../bracket`, and where it is seen from there, so
1774    /// that a step of it picks what it is built against (see
1775    /// [`geop_core_program::placed::OUTSIDE`]). Those placed after it it
1776    /// may not read: they are only drawn.
1777    fn beside(&self, instance: &str, pose: &Pose, view: Rc<PartView>) -> Rc<PartView> {
1778        // The paths of the parts placed beside it, before it, and how many
1779        // levels up from it each is.
1780        let segments: Vec<&str> = instance.split(INSTANCE_SEPARATOR).collect();
1781        let mut before: Vec<(String, usize)> = Vec::new();
1782        let mut outer = String::new();
1783        for (k, name) in segments.iter().enumerate() {
1784            let part = match outer.is_empty() {
1785                true => &self.build.part,
1786                false => match self.build.part.placed_at_path(&outer) {
1787                    Some(placed) => &*placed.part,
1788                    None => return view,
1789                },
1790            };
1791            let placed = part.placed();
1792            let Some(at) = placed.iter().position(|(placed, _, _)| placed == name) else {
1793                return view;
1794            };
1795            let up = segments.len() - k;
1796            before.extend((placed.iter().take(at)).map(|(other, _, _)| {
1797                (geop_core_program::placed::instance_path(&outer, other), up)
1798            }));
1799            outer = geop_core_program::placed::instance_path(&outer, name);
1800        }
1801        let Ok(drawn) = self.draw(&self.build.part.current(), &self.build.runner) else {
1802            return view;
1803        };
1804        let back = pose.inverse();
1805        let origin = CoordinateSystem::world_at(Vector3::zero());
1806        let mut with = (*view).clone();
1807        for other in &drawn.instances {
1808            let Some((path, up)) = before.iter().find(|(path, _)| {
1809                (other.name.strip_prefix(path.as_str()))
1810                    .is_some_and(|rest| rest.is_empty() || rest.starts_with(INSTANCE_SEPARATOR))
1811            }) else {
1812                continue;
1813            };
1814            let (outer, _) = geop_core_program::placed::split_instance_path(path);
1815            let inside = (other.name.strip_prefix(outer))
1816                .and_then(|rest| rest.strip_prefix(INSTANCE_SEPARATOR))
1817                .unwrap_or(&other.name);
1818            let pose = back.compose(&other.pose);
1819            let Ok(frame) = pose.motion().apply_frame(&origin) else {
1820                continue;
1821            };
1822            let up = format!("{OUTSIDE}{INSTANCE_SEPARATOR}").repeat(*up);
1823            with.instances.push(ViewInstance {
1824                name: format!("{up}{inside}"),
1825                frame,
1826                pose,
1827                state: None,
1828                fixed: true,
1829                ..other.clone()
1830            });
1831        }
1832        Rc::new(with)
1833    }
1834
1835    /// What the shown steps built on — sketches, and datums as a whole —
1836    /// and what the drawers draw only to be picked from (the datums of the
1837    /// parts placed, see [`crate::registry::Drawer::hidden`]), but none
1838    /// that a field picking now, from what is drawn of the kinds
1839    /// `picking`, could pick something of.
1840    fn hidden(&self, picking: Option<&[String]>, steps: usize) -> Vec<String> {
1841        let picking = picking.unwrap_or_default();
1842        let picks = |kind: &str| picking.iter().any(|p| p == kind);
1843        let view = self.open.as_ref().map(|open| &open.view);
1844        // Of the part itself, or of a part placed in it, behind its path.
1845        let in_view = |key: &str, has: &dyn Fn(&PartView, &str) -> bool| {
1846            let Some(view) = view else {
1847                return false;
1848            };
1849            let Ok(located) = Located::of_key(key) else {
1850                return false;
1851            };
1852            let part = match located.path() {
1853                "" => Some(&**view),
1854                path => (view.instances.iter())
1855                    .find(|i| i.name == path)
1856                    .map(|i| &*i.view),
1857            };
1858            part.is_some_and(|part| has(part, located.name()))
1859        };
1860        // A frame is put on a datum too.
1861        let picks_datums = picks(drawn::DATUM) || picks(drawn::FRAME);
1862        let picks_sketches =
1863            picks(drawn::SKETCH) || picks(drawn::SKETCH_POINT) || picks(drawn::SKETCH_CURVE);
1864        let pickable = |name: &str| {
1865            let sketch = picks_sketches
1866                && in_view(name, &|v, name| v.sketches.iter().any(|s| s.name == name));
1867            let datum =
1868                picks_datums && in_view(name, &|v, name| v.datums.iter().any(|d| d.name == name));
1869            sketch || datum
1870        };
1871        // A sketch built on is hidden by its name, as is a datum built on as
1872        // a whole.
1873        let built_on = |picked: &PickedElement| {
1874            let own = Some(&picked.target).filter(|t| t.path().is_empty());
1875            if let Some(sketch) = own.and_then(|t| t.name().strip_prefix("sketch:")) {
1876                return Some(sketch.to_string());
1877            }
1878            (picked.kind == drawn::DATUM && !picked.name.contains(':')).then(|| picked.name.clone())
1879        };
1880        let mut hidden: Vec<String> = (self.references.iter())
1881            .flat_map(|(_, picked)| picked)
1882            .filter_map(built_on)
1883            .filter(|name| !pickable(name))
1884            .collect();
1885        hidden.sort();
1886        hidden.dedup();
1887        // What is drawn only to be picked from — the datums of the parts
1888        // placed — is shown only while a field waits to pick it: a mate's,
1889        // say, not the whole time a part is placed.
1890        hidden.extend(
1891            (self.registry.hidden(&self.shown_part(steps)).into_iter())
1892                .filter(|name| !pickable(name)),
1893        );
1894        // The placed parts not isolated, of those drawn, when some are: a
1895        // part is shown with the parts placed in it and those it is in.
1896        let display = &self.file.display;
1897        if !display.isolated.is_empty() {
1898            let related = |name: &str, other: &str| {
1899                let inside = |a: &str, b: &str| {
1900                    (a.strip_prefix(b)).is_some_and(|rest| rest.starts_with(INSTANCE_SEPARATOR))
1901                };
1902                name == other || inside(name, other) || inside(other, name)
1903            };
1904            for instance in &self.view_of(steps).instances {
1905                let name = &instance.name;
1906                if !display.isolated.iter().any(|i| related(name, i)) && !hidden.contains(name) {
1907                    hidden.push(name.clone());
1908                }
1909            }
1910        }
1911        // Then what the user chose — but what a click could pick now is
1912        // shown, to be picked.
1913        for (name, &visible) in &display.visibility {
1914            if visible || pickable(name) {
1915                hidden.retain(|h| h != name);
1916            } else if !hidden.contains(name) {
1917                hidden.push(name.clone());
1918            }
1919        }
1920        hidden
1921    }
1922
1923    /// The part `steps` steps build, as drawn.
1924    fn view_of(&self, steps: usize) -> Rc<PartView> {
1925        (self.draw(&self.shown_part(steps), &self.build.runner)).unwrap_or_else(|_| {
1926            Rc::new(PartView::blank(Extent {
1927                center: Vector3::zero(),
1928                size: Scalar::from_f64(EMPTY_EXTENT),
1929            }))
1930        })
1931    }
1932
1933    /// The part `reader` reads, as the registry draws it: as it was drawn
1934    /// last, if it is made of the same (see [`Self::views`]), else of what
1935    /// each drawer drew of what reads the same (see [`Self::drawings`]) —
1936    /// in this process, or, kept, in an earlier one, of the program
1937    /// `runner` runs, whose part it is.
1938    fn draw(&self, reader: &Reader, runner: &Runner) -> GeopResult<Rc<PartView>> {
1939        let seen = reader.seen();
1940        let mut views = self.views.borrow_mut();
1941        let view = match views.iter().position(|(drawn, _)| *drawn == seen) {
1942            Some(k) => views.remove(k).1,
1943            None => Rc::new(self.registry.draw(reader, &self.drawings, Some(runner))?),
1944        };
1945        views.push((seen, view.clone()));
1946        if views.len() > KEPT_VIEWS {
1947            views.remove(0);
1948        }
1949        Ok(view)
1950    }
1951
1952    /// The steps of `program`, as `runner` last ran it on `part`: those
1953    /// before `runs_to` ran, failing as `error` says, the one `editing`
1954    /// edited — without the parts they place (see [`Self::with_placed`]).
1955    fn listed_steps(
1956        &self,
1957        runner: &Runner,
1958        part: &Part,
1959        program: &Program,
1960        runs_to: usize,
1961        editing: Option<&str>,
1962        error: impl Fn(&str) -> Option<String>,
1963    ) -> Vec<StepInfo> {
1964        let editors = self.registry.editors();
1965        (program.steps.iter().enumerate())
1966            .map(|(i, step)| {
1967                let listed = editors.info(&step.operation);
1968                // A summary is of the step's arguments, not of what it
1969                // built (see [`crate::editable::Editors::listed`]).
1970                let summary = (editors.listed(runner, part, program, &step.id))
1971                    .map(|form| form.dialog.summary())
1972                    .unwrap_or_default();
1973                StepInfo {
1974                    id: step.id.clone(),
1975                    kind: listed.as_ref().map_or("", |l| l.kind),
1976                    label: listed.as_ref().map_or("", |l| l.info.label),
1977                    summary,
1978                    error: (i < runs_to).then(|| error(&step.id)).flatten(),
1979                    runs: i < runs_to,
1980                    suppressed: part.suppressed().contains(&step.id),
1981                    editing: editing == Some(step.id.as_str()),
1982                    placed: Vec::new(),
1983                }
1984            })
1985            .collect()
1986    }
1987
1988    /// `steps`, of the part `part` placed at `path` — the program's own,
1989    /// for an empty one — each with the steps of the parts it placed, and
1990    /// so on however deep, each part by its path (`neck/head`), to edit
1991    /// where it is placed.
1992    fn with_placed(&self, path: &str, part: &Part, steps: &mut [StepInfo]) {
1993        let mut by_step: HashMap<&str, Vec<PlacedSteps>> = HashMap::new();
1994        for (instance, by, placed) in part.placed() {
1995            let inner = geop_core_program::placed::instance_path(path, instance);
1996            if let Some(listed) = self.placed_steps(&inner, placed) {
1997                by_step.entry(by).or_default().push(listed);
1998            }
1999        }
2000        for step in steps {
2001            step.placed = by_step.remove(step.id.as_str()).unwrap_or_default();
2002        }
2003    }
2004
2005    /// The steps of the part placed at `instance` — `neck/head` — built as
2006    /// `placed`, with the parts they place: none if its program cannot be
2007    /// read.
2008    ///
2009    /// Its own steps are listed once for every build and program — every
2010    /// screw of a size — and kept: a drag moves placed parts and builds none
2011    /// of them again, and a form of a placed standard part's sketch solves
2012    /// the sketch. The parts they place are of the instance, by its path.
2013    fn placed_steps(&self, instance: &str, placed: &PlacedPart) -> Option<PlacedSteps> {
2014        let file = placed.location.file.clone();
2015        let open = (self.open.as_ref())
2016            .filter(|open| (open.within.as_ref()).is_some_and(|w| w.instance == instance));
2017        // The one edited is listed as edited, all of it: its file holds it
2018        // only as far as the step edited, while it is.
2019        let program = match open {
2020            Some(open) => Rc::new(open.program.clone()),
2021            None => self.workspace.program(&file).ok()?,
2022        };
2023        let key = (file.clone(), Rc::as_ptr(&placed.part));
2024        if open.is_none()
2025            && let Some(listed) = self.listed.borrow().get(&key)
2026            && Rc::ptr_eq(&listed.part, &placed.part)
2027            && (Rc::ptr_eq(&listed.program, &program) || listed.program == program)
2028        {
2029            let mut steps = listed.steps.clone();
2030            self.with_placed(instance, &placed.part, &mut steps);
2031            return Some(PlacedSteps {
2032                instance: instance.to_string(),
2033                file,
2034                steps,
2035            });
2036        }
2037        let editing = open.map(|open| open.id.as_str());
2038        let failures = placed.part.failures();
2039        let error = |id: &str| {
2040            (failures.iter())
2041                .find(|(step, _)| step == id)
2042                .map(|(_, e)| e.to_string())
2043        };
2044        let runner = self.build.runner.nested(placed.location.clone());
2045        let mut steps = self.listed_steps(
2046            &runner,
2047            &placed.part,
2048            &program,
2049            program.steps.len(),
2050            editing,
2051            error,
2052        );
2053        if open.is_none() {
2054            let listed = ListedBuild {
2055                part: placed.part.clone(),
2056                program,
2057                steps: steps.clone(),
2058            };
2059            self.listed.borrow_mut().insert(key, listed);
2060        }
2061        self.with_placed(instance, &placed.part, &mut steps);
2062        Some(PlacedSteps {
2063            instance: instance.to_string(),
2064            file,
2065            steps,
2066        })
2067    }
2068
2069    fn program_state(&self) -> GeopResult<ProgramState> {
2070        let own = self.open.as_ref().filter(|open| open.within.is_none());
2071        let editing = own.filter(|open| !open.new).map(|open| open.id.as_str());
2072        let runs_to = match own.filter(|open| !self.picks_whole(open)) {
2073            Some(open) => open.index,
2074            None => self.marker.unwrap_or(self.file.steps.len()),
2075        };
2076        let editors = self.registry.editors();
2077        let errors = |id: &str| self.build.errors.get(id).map(|e| e.to_string());
2078        let (runner, part) = (&self.build.runner, &self.build.part);
2079        let mut steps = self.listed_steps(runner, part, &self.file, runs_to, editing, errors);
2080        self.with_placed("", part, &mut steps);
2081        // What no part placed now, however deep, is built as is listed no
2082        // more.
2083        let mut builds: BTreeSet<*const Part> = BTreeSet::new();
2084        let mut parts = vec![part];
2085        while let Some(part) = parts.pop() {
2086            for (_, _, placed) in part.placed() {
2087                if builds.insert(Rc::as_ptr(&placed.part)) {
2088                    parts.push(&placed.part);
2089                }
2090            }
2091        }
2092        (self.listed.borrow_mut()).retain(|(_, build), _| builds.contains(build));
2093        let mut operations = editors.infos();
2094        operations.sort_by_key(|listed| (listed.info.group, listed.info.tier));
2095        let part = self.part();
2096        let mut panels = BTreeMap::new();
2097        let mut details = BTreeMap::new();
2098        for panel in self.registry.panels() {
2099            let (state, said) = panel.state(&self.file, &part)?;
2100            panels.insert(panel.key().to_string(), state);
2101            details.extend(said);
2102        }
2103        Ok(ProgramState {
2104            program: self.file.clone(),
2105            path: self.path.clone(),
2106            steps,
2107            marker: self.marker.unwrap_or(self.file.steps.len()),
2108            can_undo: match &self.open {
2109                Some(open) => !open.undo.is_empty(),
2110                None => !self.undo.is_empty(),
2111            },
2112            can_redo: match &self.open {
2113                Some(open) => !open.redo.is_empty(),
2114                None => !self.redo.is_empty(),
2115            },
2116            tool: (self.tool.as_ref())
2117                .filter(|in_hand| in_hand.taken)
2118                .map(|in_hand| in_hand.key.clone()),
2119            tools: self.registry.tools().into_iter().cloned().collect(),
2120            operations,
2121            exporters: self.registry.outputs().exporters(),
2122            inspectors: self.registry.outputs().inspectors(),
2123            examples: self.registry.example_names().collect(),
2124            workspace_examples: self.registry.workspace_example_names().collect(),
2125            panels,
2126            details,
2127            keys: APP_KEYS,
2128        })
2129    }
2130}