diff --git a/crates/snowbound/src/main.rs b/crates/snowbound/src/main.rs index 5aab659e493d1bb8a6f24c17f64e19de201386d9..769853cc4bf9b254a1441c15056cf406c5320a41 100644 --- a/crates/snowbound/src/main.rs +++ b/crates/snowbound/src/main.rs @@ -1039,23 +1039,32 @@ impl State { library.purge_recycle_bin(); } } + // Where the notebook was left, or its first section. let shown = notebooks .iter() .filter(|library| Some(&library.location) == current.as_ref()) .chain(¬ebooks) - .find_map(|library| Some((library, library.first_section()?))); + .find_map(|library| { + let left = stored.recent.iter().find(|place| { + place.notebook == library.location && library.contains(&place.section) + }); + match left { + Some(place) => Some((library, place.section.clone(), Some(place.page))), + None => Some((library, library.first_section()?, None)), + } + }); match shown { - Some((library, path)) if !library.locked(&path) => { + Some((library, path, left)) if !library.locked(&path) => { let section = library.open(&path, notify(proxy.clone()))?; let (opened, page) = - read_session(section, Arc::clone(library), path, None)?; + read_session(section, Arc::clone(library), path, left)?; let (scene, editor) = PageScene::from_page(page, &mut engine)?; session = Some(opened); (editor, Some((scene, [0.0; 2]))) } shown => { // A first section still locked shows its locked page. - locked = shown.map(|(library, path)| protection::Locked { + locked = shown.map(|(library, path, _)| protection::Locked { library: Arc::clone(library), path, }); @@ -1228,7 +1237,9 @@ impl State { opening: None, switching: None, places: HashMap::new(), - last_pages: HashMap::new(), + last_pages: (stored.recent.iter().rev()) + .map(|place| (library::key(&place.notebook, &place.section), place.page)) + .collect(), trail: { let mut trail = navigation::Trail::default(); trail.recent = stored.recent; diff --git a/crates/snowbound/src/settings.rs b/crates/snowbound/src/settings.rs index 569cd42bed2fe5b6812132af90cd4e9785c81d0f..55518123a37062e05177d016933d8c6587090826 100644 --- a/crates/snowbound/src/settings.rs +++ b/crates/snowbound/src/settings.rs @@ -46,7 +46,7 @@ pub struct Settings { pub servers: Vec, /// Chords set in Options' Keyboard, by command, each list in place of its defaults. pub keys: std::collections::BTreeMap>, - /// Pages shown lately, latest first, which the palette lists first. + /// Pages shown lately, latest first, which the palette lists first and a launch returns to. pub recent: Vec, pub passwords: Passwords, } diff --git a/crates/snowbound/src/sidebar.rs b/crates/snowbound/src/sidebar.rs index 743fddbe1588504d3d60c74e061bbbea01c217a1..140ce8754ba58f5897bf2dc0c1f32a92679b739d 100644 --- a/crates/snowbound/src/sidebar.rs +++ b/crates/snowbound/src/sidebar.rs @@ -1100,8 +1100,8 @@ impl crate::State { self.read_notebook(location, section, open, read); } - /// Shows `section`, or the first section, of the notebook at `location`, `open` or read - /// with `read`, in place of the notebook listed there. + /// Shows `section`, or where it was left, or the first section, of the notebook at + /// `location`, `open` or read with `read`, in place of the notebook listed there. pub(crate) fn read_notebook( &mut self, location: String, @@ -1109,6 +1109,15 @@ impl crate::State { open: Option>, read: impl FnOnce(&str, &std::path::Path) -> Result + Send + 'static, ) { + let section = section.or_else(|| { + (self.trail.recent.iter()) + .find(|place| place.notebook == location) + .map(|place| place.section.clone()) + }); + let left = section + .as_ref() + .and_then(|path| self.last_pages.get(&crate::library::key(&location, path))) + .copied(); let (cache, notify) = (self.cache.clone(), crate::notify(self.proxy.clone())); self.load(move || { let library = match open { @@ -1122,9 +1131,10 @@ impl crate::State { if let Err(error) = &library.notebook { return Err(error.clone().into()); } - let Some(path) = section + let Some((path, left)) = section .filter(|path| library.contains(path)) - .or_else(|| library.first_section()) + .map(|path| (path, left)) + .or_else(|| Some((library.first_section()?, None))) else { // Shown once its sections arrive. if library.downloading() { @@ -1133,7 +1143,7 @@ impl crate::State { return Err("This folder holds no notebook sections.".into()); }; let section = library.open(&path, notify)?; - let (session, page) = crate::read_session(section, library, path, None)?; + let (session, page) = crate::read_session(section, library, path, left)?; Ok(crate::Loaded::Section(Box::new(session), page)) }); } diff --git a/crates/snowbound/tests/replay.rs b/crates/snowbound/tests/replay.rs index 5e19e767a0881677c33a52b734045914410cec8e..b867e7bb24f1d887ff62a3b0771c3ac9c15601d3 100644 --- a/crates/snowbound/tests/replay.rs +++ b/crates/snowbound/tests/replay.rs @@ -12,6 +12,11 @@ impl Scratch { let path = std::env::temp_dir().join(format!("snowbound-{name}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&path); std::fs::create_dir_all(path.join("notebook")).unwrap(); + std::fs::write( + path.join("settings.json"), + r#"{"user_name": "Snowbound Test"}"#, + ) + .unwrap(); Self(path) } } @@ -22,7 +27,8 @@ impl Drop for Scratch { } } -/// Runs `steps` against a copy of `notebook`'s files, then returns the trees each +/// Runs `steps` against a copy of `notebook`'s files with the scratch's settings, then +/// returns the trees each /// `accessibility` step named by the trailing word wrote. fn replay(scratch: &Scratch, notebook: Option<&Path>, steps: &[&str]) -> Vec { let dir = &scratch.0; @@ -46,11 +52,6 @@ fn replay(scratch: &Scratch, notebook: Option<&Path>, steps: &[&str]) -> Vec