From 416e66500bdd9da07c940e34af143a7033c8cf87 Mon Sep 17 00:00:00 2001 From: "KOSMOS, Tzushih.K" <48860861+kisaraki@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:50:24 +0800 Subject: [PATCH] feat: start file dialogs on desktop --- Cargo.lock | 110 +++++++++++++++++++++-- crates/edit/Cargo.toml | 1 + crates/edit/src/bin/edit/draw_editor.rs | 2 +- crates/edit/src/bin/edit/draw_menubar.rs | 4 +- crates/edit/src/bin/edit/main.rs | 9 +- crates/edit/src/bin/edit/state.rs | 36 ++++++++ crates/edit/src/sys/mod.rs | 8 ++ 7 files changed, 157 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4afe7a208b9..2eac7a0e596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,7 +66,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -251,11 +251,33 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "edit" version = "2.0.0" dependencies = [ "criterion", + "dirs", "libc", "lsh", "stdext", @@ -277,6 +299,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -354,7 +387,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom", + "getrandom 0.3.4", "libc", ] @@ -374,6 +407,15 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libredox" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +dependencies = [ + "libc", +] + [[package]] name = "log" version = "0.4.29" @@ -424,6 +466,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "page_size" version = "0.6.0" @@ -518,6 +566,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.12.3" @@ -598,7 +657,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -644,6 +703,37 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -697,6 +787,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -738,7 +834,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -813,7 +909,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -824,7 +920,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -892,7 +988,7 @@ checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/crates/edit/Cargo.toml b/crates/edit/Cargo.toml index 02becfdb908..2ea1567b297 100644 --- a/crates/edit/Cargo.toml +++ b/crates/edit/Cargo.toml @@ -19,6 +19,7 @@ harness = false debug-latency = [] [dependencies] +dirs = "6.0.0" lsh.workspace = true stdext.workspace = true diff --git a/crates/edit/src/bin/edit/draw_editor.rs b/crates/edit/src/bin/edit/draw_editor.rs index ce03bbeb4cb..a70b70e2864 100644 --- a/crates/edit/src/bin/edit/draw_editor.rs +++ b/crates/edit/src/bin/edit/draw_editor.rs @@ -199,7 +199,7 @@ pub fn draw_handle_save(ctx: &mut Context, state: &mut State) { } } else { // No path? Show the file picker. - state.wants_file_picker = StateFilePicker::SaveAs; + show_file_picker(state, StateFilePicker::SaveAs); state.wants_save = false; ctx.needs_rerender(); } diff --git a/crates/edit/src/bin/edit/draw_menubar.rs b/crates/edit/src/bin/edit/draw_menubar.rs index 401614786a1..019f34a8607 100644 --- a/crates/edit/src/bin/edit/draw_menubar.rs +++ b/crates/edit/src/bin/edit/draw_menubar.rs @@ -43,14 +43,14 @@ fn draw_menu_file(ctx: &mut Context, state: &mut State) { draw_add_untitled_document(ctx, state); } if ctx.menubar_menu_button(loc(LocId::FileOpen), 'O', kbmod::CTRL | vk::O) { - state.wants_file_picker = StateFilePicker::Open; + show_file_picker(state, StateFilePicker::Open); } if state.documents.active().is_some() { if ctx.menubar_menu_button(loc(LocId::FileSave), 'S', kbmod::CTRL | vk::S) { state.wants_save = true; } if ctx.menubar_menu_button(loc(LocId::FileSaveAs), 'A', vk::NULL) { - state.wants_file_picker = StateFilePicker::SaveAs; + show_file_picker(state, StateFilePicker::SaveAs); } } #[allow(irrefutable_let_patterns)] diff --git a/crates/edit/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs index 27ae7fab6c0..459a22a3dc2 100644 --- a/crates/edit/src/bin/edit/main.rs +++ b/crates/edit/src/bin/edit/main.rs @@ -301,7 +301,10 @@ fn handle_args(state: &mut State) -> apperr::Result { dir = Some(parent.to_path_buf()); } - state.file_picker_pending_dir = DisplayablePathBuf::from_path(dir.unwrap_or(cwd)); + // EN: With no explicit file or directory, file dialogs begin on the desktop. + // 中文:未指定檔案或目錄時,檔案對話框預設由桌面開始。 + let default_dir = sys::desktop_dir().unwrap_or(cwd); + state.file_picker_pending_dir = DisplayablePathBuf::from_path(dir.unwrap_or(default_dir)); Ok(false) } @@ -381,11 +384,11 @@ fn draw(ctx: &mut Context, state: &mut State) { if key == kbmod::CTRL | vk::N { draw_add_untitled_document(ctx, state); } else if key == kbmod::CTRL | vk::O { - state.wants_file_picker = StateFilePicker::Open; + show_file_picker(state, StateFilePicker::Open); } else if key == kbmod::CTRL | vk::S { state.wants_save = true; } else if key == kbmod::CTRL_SHIFT | vk::S { - state.wants_file_picker = StateFilePicker::SaveAs; + show_file_picker(state, StateFilePicker::SaveAs); } else if key == kbmod::CTRL | vk::W { state.wants_close = true; } else if key == kbmod::CTRL | vk::P { diff --git a/crates/edit/src/bin/edit/state.rs b/crates/edit/src/bin/edit/state.rs index 13a1cefbbea..a6cdad1a581 100644 --- a/crates/edit/src/bin/edit/state.rs +++ b/crates/edit/src/bin/edit/state.rs @@ -15,6 +15,7 @@ use edit::{buffer, icu}; use crate::apperr; use crate::documents::DocumentManager; use crate::localization::*; +use crate::sys; #[repr(transparent)] pub struct FormatApperr(apperr::Error); @@ -243,6 +244,41 @@ impl State { } } +/// EN: Opens a file picker and selects a useful cross-platform starting directory. +/// 中文:開啟檔案選擇器,並選擇適用於各平台的起始資料夾。 +pub fn show_file_picker(state: &mut State, picker: StateFilePicker) { + debug_assert!(matches!(picker, StateFilePicker::Open | StateFilePicker::SaveAs)); + + // EN: Open starts on the desktop. Save As keeps a named document beside its source, + // EN: while a new untitled document starts on the desktop as well. + // 中文:開啟舊檔由桌面開始;另存已命名檔案時沿用來源目錄,未命名檔案則由桌面開始。 + let target_dir = if picker == StateFilePicker::Open { + sys::desktop_dir().ok() + } else { + state + .documents + .active() + .and_then(|doc| doc.path.as_deref()) + .and_then(Path::parent) + .map(Path::to_path_buf) + .or_else(|| sys::desktop_dir().ok()) + }; + + if let Some(target_dir) = target_dir + && state.file_picker_pending_dir.as_path() != target_dir + { + state.file_picker_pending_dir = DisplayablePathBuf::from_path(target_dir); + state.file_picker_pending_dir_revision = + state.file_picker_pending_dir_revision.wrapping_add(1); + } + + state.wants_file_picker = picker; + state.file_picker_pending_name.clear(); + state.file_picker_entries = None; + state.file_picker_overwrite_warning = None; + state.file_picker_autocomplete.clear(); +} + pub fn draw_add_untitled_document(ctx: &mut Context, state: &mut State) { if let Err(err) = state.documents.add_untitled() { error_log_add(ctx, state, err); diff --git a/crates/edit/src/sys/mod.rs b/crates/edit/src/sys/mod.rs index 91d59881248..09e390f40e7 100644 --- a/crates/edit/src/sys/mod.rs +++ b/crates/edit/src/sys/mod.rs @@ -15,3 +15,11 @@ pub use std::fs::canonicalize; pub use unix::*; #[cfg(windows)] pub use windows::*; + +/// EN: Returns the user's desktop directory through the platform-neutral directory provider. +/// 中文:透過跨平台目錄提供者取得使用者的桌面資料夾。 +pub fn desktop_dir() -> std::io::Result { + dirs::desktop_dir().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::NotFound, "desktop directory is unavailable") + }) +}