diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 8c0555ea9..9895f1c56 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3094,7 +3094,7 @@ fn recent(app: &mut VectorcraftApp, ui: &mut Ui) { for path in files { if recent_row(ui, &path).clicked() { // A failure is reported to the user there. - let _ = crate::io::open_reporting(app, &path); + let _ = crate::io::open_recent_reporting(app, &path); } } } diff --git a/crates/ui-egui/src/io.rs b/crates/ui-egui/src/io.rs index ae01b0305..7290da43b 100644 --- a/crates/ui-egui/src/io.rs +++ b/crates/ui-egui/src/io.rs @@ -214,6 +214,44 @@ pub fn open_reporting(app: &mut VectorcraftApp, path: &str) -> Result Result { + if let Some(r) = try_activate_open_document(app, path)? { + return Ok(r); + } + open_reporting(app, path) +} + +fn try_activate_open_document(app: &mut VectorcraftApp, path: &str) -> Result, String> { + let Some(index) = document_index_for_path(app, path) else { + return Ok(None); + }; + let st = &app.session.documents()[index]; + let r = json!({ + "index": index, + "title": st.title(), + "format": st.format, + "warnings": [], + "restored": false, + "activated": true, + }); + app.run("document.activate", json!({ "index": index }))?; + opened(app, &r); + Ok(Some(r)) +} + +fn document_index_for_path(app: &VectorcraftApp, path: &str) -> Option { + app.session.documents().iter().position(|st| st.path.as_deref().is_some_and(|p| same_open_path(p, path))) +} + +fn same_open_path(a: &str, b: &str) -> bool { + match (std::fs::canonicalize(a), std::fs::canonicalize(b)) { + (Ok(a), Ok(b)) => a == b, + _ => a == b, + } +} + fn read(app: &VectorcraftApp, path: &str) -> Result, String> { app.services.read.as_ref().ok_or("no file reader")?(path) } diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 16d1fd808..022c85ba4 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1266,7 +1266,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option Ok(io::cancel_exports(app)), "file.exportInBackground" => Ok(io::hide_exports(app)), id if id.starts_with("file.openRecent") => match recent_slot(app, id).cloned() { - Some(path) => io::open_reporting(app, &path), + Some(path) => io::open_recent_reporting(app, &path), None => Err("no such recent file".into()), }, "type.findFont" => { diff --git a/crates/ui-egui/src/tests_home.rs b/crates/ui-egui/src/tests_home.rs index 4725552cc..86785557f 100644 --- a/crates/ui-egui/src/tests_home.rs +++ b/crates/ui-egui/src/tests_home.rs @@ -297,3 +297,31 @@ fn home_scrolls_in_a_short_window() { visible_home_texts(&mut app, &ctx, size, 4.1, vec![press(false)]); assert_eq!(app.session.documents().len(), 1, "the card opened its preset"); } + +/// Home › Recent Files and File › Open Recent focus an open tab instead of opening a second copy +/// (#3455); `document.open` is unchanged. +#[test] +fn home_recent_reopen_focuses_an_already_open_document() { + let services = crate::Services { + read: Some(Box::new(|p: &str| std::fs::read(p).map_err(|e| e.to_string()))), + write: Some(Box::new(|p: &str, b: &[u8]| std::fs::write(p, b).map_err(|e| e.to_string()))), + ..Default::default() + }; + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), services); + let d = std::env::temp_dir().join(format!("vc-home-reopen-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&d); + std::fs::create_dir_all(&d).unwrap(); + let path = d.join("doc.vectorcraft").to_string_lossy().to_string(); + app.run("file.new", json!({"width": 100, "height": 100})).unwrap(); + app.run("shape.rectangle", json!({"x": 1, "y": 2, "width": 10, "height": 10})).unwrap(); + app.run("file.saveAs", json!({"path": path})).unwrap(); + app.run("file.new", json!({})).unwrap(); + assert_eq!(app.session.documents().len(), 2); + assert_eq!(app.session.active_index(), Some(1)); + let r = crate::io::open_recent_reporting(&mut app, &path).unwrap(); + assert_eq!(r["activated"], true); + assert_eq!(app.session.documents().len(), 2); + assert_eq!(app.session.active_index(), Some(0)); + assert_eq!(app.session.doc().unwrap().doc.layers[0].children().unwrap().len(), 1); + let _ = std::fs::remove_dir_all(d); +}