1mod 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
78const KEPT_BUILDS: usize = 6;
81
82type Drawing = (Seen, Rc<PartView>);
84
85const KEPT_VIEWS: usize = 4;
89
90type EditorFiles = Files<Box<dyn FileStoreMut>>;
94
95type Edited = (serde_json::Value, StateValues);
98
99struct Open {
101 index: usize,
103 id: String,
105 new: bool,
108 program: Program,
112 editor: StepEditor,
113 view: Rc<PartView>,
116 undo: Vec<Edited>,
119 redo: Vec<Edited>,
120 dragging: Option<Edited>,
123 reopened: Option<Edited>,
127 within: Option<Within>,
130}
131
132struct Within {
140 instance: String,
142 location: Location,
144 text: String,
146 runner: Runner,
148 part: Rc<Part>,
150 pose: Pose,
152}
153
154impl Open {
155 fn step(&self) -> GeopResult<&ProgramStep> {
157 self.program.step(&self.id)
158 }
159
160 fn edited(&self) -> GeopResult<Edited> {
163 Ok((self.step()?.args.clone(), self.program.state.clone()))
164 }
165}
166
167#[derive(Debug)]
170enum PlacedStep {
171 Existing(String),
172 New(String),
173}
174
175enum Undone {
179 Program(Program),
180 File {
181 path: String,
182 program: Program,
183 },
184 Cancelled(Box<Open>),
187}
188
189struct InHand {
193 key: String,
194 tool: Box<dyn Tool>,
195 taken: bool,
196}
197
198#[derive(Clone, PartialEq)]
205struct Shown {
206 seen: Seen,
207 steps: usize,
208 hidden: Vec<String>,
209 colors: BTreeMap<String, String>,
212 sheet: Option<([f64; 3], f64)>,
214}
215
216#[derive(PartialEq)]
219struct Sent {
220 component: String,
221 frame: [f64; 12],
222 color: Option<String>,
223}
224
225pub struct Editor {
227 registry: Rc<Registry>,
229 operations: Rc<OperationRegistry>,
231 file: Program,
232 path: Option<String>,
235 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 builds: Vec<(String, Build)>,
247 run: u64,
249 references: Vec<(ProgramStep, Vec<PickedElement>)>,
254 shown: Option<Shown>,
255 added: Option<Vec<File>>,
257 written: Vec<File>,
259 exported: Option<Export>,
261 reporting: bool,
263 placed: Option<HashMap<String, Sent>>,
266 kept: Kept,
269 tool: Option<InHand>,
271 asked: Option<(String, serde_json::Value, Vec<PickedElement>)>,
274 drawn: Option<Rc<PartView>>,
276 views: RefCell<Vec<Drawing>>,
281 drawings: Drawings,
285 lights: Vec<PickedElement>,
287 lit: Option<Lighting>,
289 listing: Option<(Vec<String>, Vec<StructureItem>)>,
293 listed: RefCell<HashMap<(String, *const Part), ListedBuild>>,
296}
297
298struct ListedBuild {
301 part: Rc<Part>,
302 program: Rc<Program>,
303 steps: Vec<StepInfo>,
304}
305
306impl Editor {
307 pub fn new(registry: Registry, files: impl FileStoreMut + 'static) -> Self {
311 Self::with_cache(registry, files, Cache::new())
312 }
313
314 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 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 pub fn forget_viewer(&mut self) {
380 self.shown = None;
381 self.placed = None;
382 }
383
384 pub fn registry(&self) -> &Registry {
386 &self.registry
387 }
388
389 pub fn part(&self) -> Reader<'_> {
391 self.build.part.current()
392 }
393
394 pub fn counts(&self) -> Counts {
396 self.build.runner.counts()
397 }
398
399 pub fn editing(&self) -> Option<&ProgramStep> {
401 self.open.as_ref().and_then(|open| open.step().ok())
402 }
403
404 pub fn step_editor(&self) -> Option<&StepEditor> {
407 self.open.as_ref().map(|open| &open.editor)
408 }
409
410 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 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 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 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 fn run_to(&mut self, end: Option<usize>) {
448 self.run_staged(end, self.preview);
449 }
450
451 fn run_staged(&mut self, end: Option<usize>, edited: bool) {
457 self.run_placed(end, edited, false);
458 }
459
460 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 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 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 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 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 pub fn handle(&mut self, command: Command) -> Update {
532 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 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 if let Some(step) = &step {
564 hidden.extend(step.presentation.hidden.iter().cloned());
565 }
566 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 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 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 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 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 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 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 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 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 fn open(&mut self, program: Program, index: usize, id: String, new: bool) -> GeopResult<()> {
809 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 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(¤t, &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 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 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 fn apply(&mut self, command: Command) -> GeopResult<Changed> {
939 Ok(match command {
940 Command::Show => {
941 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 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 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 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 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 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 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 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 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 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 Some(Open {
1137 within: Some(within),
1138 ..
1139 }) => {
1140 (self.workspace).write(&within.location.file, Some(within.text));
1141 }
1142 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 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 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 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 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 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 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 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 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 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 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 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 Changed::Run
1479 }
1480 })
1481 }
1482
1483 fn settle(&mut self) {
1500 let registry = self.registry.clone();
1501 let Some(settler) = registry.get_settler() else {
1502 return;
1503 };
1504 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 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 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 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 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 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 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 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 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 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 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 let error = (missing.is_empty() && self.preview)
1677 .then(|| self.step_error(open))
1678 .flatten();
1679 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 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 fn shown_part(&self, steps: usize) -> Reader<'_> {
1718 self.build.view_at(self.current(), steps)
1719 }
1720
1721 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 fn picks_whole(&self, open: &Open) -> bool {
1729 open.step().is_ok_and(|step| self.step_picks_whole(step))
1730 }
1731
1732 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 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 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 fn beside(&self, instance: &str, pose: &Pose, view: Rc<PartView>) -> Rc<PartView> {
1778 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 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 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 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 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 hidden.extend(
1891 (self.registry.hidden(&self.shown_part(steps)).into_iter())
1892 .filter(|name| !pickable(name)),
1893 );
1894 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 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 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 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 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 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 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 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 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 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}