From fff62273519e9343b4bb380f62c66853eb6658dd Mon Sep 17 00:00:00 2001 From: nrq6993-a11y Date: Mon, 26 Oct 2026 20:34:53 +0700 Subject: [PATCH 1/2] =?UTF-8?q?#4122:=20File=20=E2=86=92=20Open=20starts?= =?UTF-8?q?=20in=20the=20last=20opened=20file's=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed from #4263 onto main. --- crates/effects/src/group.rs | 2 -- crates/engine/src/cmd/fileio/mod.rs | 2 +- crates/engine/src/cmd/fileio/save.rs | 24 +++++++++++++++++++++++- crates/ui-egui/src/io.rs | 9 ++++++++- crates/ui-egui/src/tests_picks.rs | 18 +++++++++++++++++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/crates/effects/src/group.rs b/crates/effects/src/group.rs index 9f0eb549d..6f9cae3bc 100644 --- a/crates/effects/src/group.rs +++ b/crates/effects/src/group.rs @@ -116,8 +116,6 @@ pub const PATHFINDER_MAX_OVERLAPS: usize = 16_384; /// Fenwick tree of counts over `n` slots, for counting the active boxes of [`within_budget`]. struct Counts(Vec); -// `isolate_lowest_one` is still unstable in Rust 1.96, and the workspace builds on 1.95 (rust-version). -#[allow(unknown_lints, clippy::manual_isolate_lowest_one)] impl Counts { fn add(&mut self, slot: usize, d: isize) { let mut i = slot + 1; diff --git a/crates/engine/src/cmd/fileio/mod.rs b/crates/engine/src/cmd/fileio/mod.rs index 2fb4cec00..4b05cb8ab 100644 --- a/crates/engine/src/cmd/fileio/mod.rs +++ b/crates/engine/src/cmd/fileio/mod.rs @@ -74,7 +74,7 @@ pub use native::with_compression_pref; pub use pdfimport::{LoadOptions, page_document}; pub(crate) use save::job_for; pub use save::{ - SAVE_FORMATS, SaveJob, SaveMode, SavePlan, StreamWrite, TEMPLATE_FORMATS, export_folder, reads_option as save_reads_option, + SAVE_FORMATS, SaveJob, SaveMode, SavePlan, StreamWrite, TEMPLATE_FORMATS, dialog_folder, export_folder, reads_option as save_reads_option, remembered_save_options, save_filters, save_format, save_job, save_plan, save_with, stamp_save_dates, template_filters, templates_dialog_folder, templates_folder, }; diff --git a/crates/engine/src/cmd/fileio/save.rs b/crates/engine/src/cmd/fileio/save.rs index 1d70e99f2..486777a24 100644 --- a/crates/engine/src/cmd/fileio/save.rs +++ b/crates/engine/src/cmd/fileio/save.rs @@ -185,6 +185,14 @@ fn parent_folder(path: &str) -> Option { std::path::Path::new(path).parent().map(|p| p.to_string_lossy().to_string()).filter(|p| !p.is_empty()) } +/// A file dialog's starting folder for a path on disk (#4122): the path's parent when it is a +/// folder, else the nearest ancestor directory that exists. +pub fn dialog_folder(path: &str) -> Option { + let folder = parent_folder(path)?; + let path = std::path::PathBuf::from(folder); + path.ancestors().find(|a| a.is_dir()).map(|a| a.to_string_lossy().into_owned()) +} + /// A save format by id or extension, or why it isn't one. fn save_format_of(f: &str) -> std::result::Result<&'static Format, String> { match format(f) { @@ -300,7 +308,7 @@ fn disagreeing_option(p: &Value, svg: bool) -> std::result::Result Result Option { + app.ui.recent_files.iter().find_map(|p| fileio::dialog_folder(p)).or_else(|| suggested(app, "").1) +} + /// File → Open… pub fn open_dialog(app: &mut VectorcraftApp) -> Result<(), String> { if let Some(f) = app.services.open_async.as_mut() { f(); return Ok(()); } - let path = pick_open(app, &FilePick { filters: FilePick::filters(fileio::open_filters()), ..Default::default() })?; + let path = + pick_open(app, &FilePick { filters: FilePick::filters(fileio::open_filters()), folder: open_dialog_folder(app), ..Default::default() })?; open_reporting(app, &path).map(|_| ()) } diff --git a/crates/ui-egui/src/tests_picks.rs b/crates/ui-egui/src/tests_picks.rs index ece5aa444..686fddd18 100644 --- a/crates/ui-egui/src/tests_picks.rs +++ b/crates/ui-egui/src/tests_picks.rs @@ -154,12 +154,28 @@ fn saved_document(app: &mut VectorcraftApp, tag: &str) -> (std::path::PathBuf, S // ------------------------------------------------------------------------------------- the tests +#[test] +fn file_open_starts_in_the_last_opened_files_folder() { + let (mut app, shown, _) = app(); + let dir = std::env::temp_dir().join(format!("vc-open-dialog-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("poster.svg").to_string_lossy().into_owned(); + crate::io::note_recent(&mut app, &path); + assert!(app.run("file.open", json!({})).is_err()); + poll(&mut app); + let folder = dir.to_string_lossy().into_owned(); + assert!(matches!(shown.requests().as_slice(), [PickRequest::Open(p)] if p.folder == Some(folder))); + shown.answer(&[]); + poll(&mut app); + let _ = std::fs::remove_dir_all(&dir); +} + #[test] fn file_open_opens_what_its_dialog_picks_when_it_answers() { let (mut app, shown, _) = app(); // The dialog shows; File › Open gives up for now, without a "cancelled". assert!(app.run("file.open", json!({})).is_err()); - assert!(matches!(shown.requests().as_slice(), [PickRequest::Open(_)])); + assert!(matches!(shown.requests().as_slice(), [PickRequest::Open(p)] if p.folder.is_none())); poll(&mut app); assert!(app.session.documents().is_empty() && app.ui.status.is_empty(), "{}", app.ui.status); // Another dialog waits for this one. From 749523d99c39c4df31573d873a469036e6110012 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 26 Oct 2026 20:35:04 +0700 Subject: [PATCH 2/2] Effects: keep the isolate_lowest_one allow #4263 dropped (clippy 1.99, the pinned toolchain, still flags the manual form) --- crates/effects/src/group.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/effects/src/group.rs b/crates/effects/src/group.rs index 6f9cae3bc..9f0eb549d 100644 --- a/crates/effects/src/group.rs +++ b/crates/effects/src/group.rs @@ -116,6 +116,8 @@ pub const PATHFINDER_MAX_OVERLAPS: usize = 16_384; /// Fenwick tree of counts over `n` slots, for counting the active boxes of [`within_budget`]. struct Counts(Vec); +// `isolate_lowest_one` is still unstable in Rust 1.96, and the workspace builds on 1.95 (rust-version). +#[allow(unknown_lints, clippy::manual_isolate_lowest_one)] impl Counts { fn add(&mut self, slot: usize, d: isize) { let mut i = slot + 1;