diff --git a/crates/snowbound/src/undo.rs b/crates/snowbound/src/undo.rs index 7760f835127ba86ff52304ce99191c68015c7cfa..39172b1b05ad348bb27d7ef4f7685f8759d8ccc4 100644 --- a/crates/snowbound/src/undo.rs +++ b/crates/snowbound/src/undo.rs @@ -411,7 +411,7 @@ impl Site { /// Does `change` to the section's pages, `shown` the page open; `None` where what it /// changes is gone or holds what it did not make, which it leaves alone. async fn change( - &self, + &mut self, change: Change, shown: ExGuid, ) -> Result, Box> { @@ -454,10 +454,9 @@ impl Site { let (show, added) = self.after(&listed, &pages, show, &mut ops); if !created && binned { let pages: Vec = kept.iter().map(|kept| kept.page.clone()).collect(); - self.library - .reopen()? - .recycle_pages_async(&pages, &self.author) - .await?; + let mut notebook = self.library.reopen()?; + notebook.recycle_pages_async(&pages, &self.author).await?; + self.library = Arc::new(self.library.with(notebook)); self.replica.written_async().await?; } self.apply(ops).await?; @@ -508,10 +507,9 @@ impl Site { if !created && binned { let identities: Vec<[u8; 16]> = back.iter().filter_map(|kept| kept.page.identity).collect(); - self.library - .reopen()? - .unrecycle_pages_async(&identities) - .await?; + let mut notebook = self.library.reopen()?; + notebook.unrecycle_pages_async(&identities).await?; + self.library = Arc::new(self.library.with(notebook)); } Applied { undo: Change::Delete { @@ -1062,13 +1060,29 @@ impl State { }; self.persist()?; let session = self.session.as_ref().ok_or("No section is open")?; - let site = Site { + let changing = if matches!( + &change, + Change::Delete { created: false, .. } | Change::Restore { created: false, .. } + ) && matches!(session.library.notebook, Ok(Some(_))) + { + Some( + session + .library + .structure + .try_lock_arc() + .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::WouldBlock))?, + ) + } else { + None + }; + let mut site = Site { replica: Arc::clone(session.section.replica()), library: Arc::clone(&session.library), author: self.author.clone(), fresh: self.dated_page(None, "")?, }; let shown = session.space; + let original = Arc::clone(&site.library); let proxy = self.proxy.clone(); self.undo.busy += 1; self.switching = Some((None, web_time::Instant::now())); @@ -1080,8 +1094,34 @@ impl State { }; let failed = applied.as_ref().err().map(ToString::to_string); let (stale, applied) = (failed.is_none(), applied.ok().flatten()); + let library = Arc::clone(&site.library); let _ = proxy.send_event(UserEvent::Then(Box::new(move |state: &mut State| { state.undo.busy -= 1; + let lineage = |listed: &Library| { + listed.location == original.location + && Arc::ptr_eq(&listed.structure, &original.structure) + }; + if !Arc::ptr_eq(&original, &library) + && state.notebooks.iter().any(|listed| lineage(listed)) + { + let path = state + .session + .as_ref() + .filter(|session| lineage(&session.library)) + .map(|session| session.tabs[session.tab].path.clone()); + if let Some(path) = path { + state.adopt(Arc::clone(&library), Some(&path))?; + } else { + state.list(&library); + if state + .sectionless + .as_ref() + .is_some_and(|listed| lineage(listed)) + { + state.sectionless = Some(library); + } + } + } let Some(applied) = applied else { // What it would change is gone: Undo goes on to the step before. return match taken { @@ -1102,6 +1142,7 @@ impl State { state.edited(Vec::new()); Ok(()) }))); + drop(changing); if let Some(failed) = failed { return Err(failed.into()); } @@ -1426,21 +1467,41 @@ mod tests { fn deleted_pages_come_back_where_they_were_and_leave_the_bin() { let fixture = Fixture::new("delete", &["First", "Second", "Third"]); let second = fixture.space("Second"); - let deleted = fixture - .change(Change::Delete { + let mut site = fixture.site(); + assert!(site.library.tabs(crate::recycle::BIN).is_empty()); + let deleted = pollster::block_on(site.change( + Change::Delete { pages: vec![second], show: None, created: false, - }) - .unwrap(); + }, + fixture.space("First"), + )) + .unwrap() + .unwrap(); + assert!(!site.library.tabs(crate::recycle::BIN).is_empty()); + assert!(!Arc::ptr_eq(&site.library, &fixture.library)); + assert!(Arc::ptr_eq( + &site.library.structure, + &fixture.library.structure + )); + assert!(Arc::ptr_eq(&site.replica, fixture.section().replica())); + assert!(fixture.library.tabs(crate::recycle::BIN).is_empty()); + let deleted_library = Arc::clone(&site.library); assert_eq!(fixture.titles(), titled(&["First", "Third"])); assert_eq!(deleted.show, fixture.space("Third")); assert_eq!(fixture.binned(), ["Second"]); - let restored = fixture.change(deleted.undo).unwrap(); + let restored = pollster::block_on(site.change(deleted.undo, deleted.show)) + .unwrap() + .unwrap(); + assert!(!Arc::ptr_eq(&site.library, &deleted_library)); + assert!(!site.library.tabs(crate::recycle::BIN).is_empty()); assert_eq!(fixture.titles(), titled(&["First", "Second", "Third"])); assert_eq!(restored.show, fixture.space("Second")); assert!(fixture.binned().is_empty()); - let again = fixture.change(restored.undo).unwrap(); + let again = pollster::block_on(site.change(restored.undo, restored.show)) + .unwrap() + .unwrap(); assert_eq!(fixture.titles(), titled(&["First", "Third"])); assert_eq!(fixture.binned(), ["Second"]);