From f78d715fee81efed7dce26ff6505403744a69a2e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 10 Nov 2026 18:23:33 -0800 Subject: [PATCH] Scripts: a Scripts folder read asked for during another starts when that one ends A File menu opening or a failed ui.runInstalledScript while a read of the Scripts folder was under way was dropped, so a script added after that read passed the folder stayed unlisted until the next window activation or File menu opening. The request is now kept and the next frame starts the read once the running one is taken in. --- crates/ui-egui/src/scripts_menu.rs | 34 +++++++++++++++++++++--- crates/ui-egui/src/tests/scripts_menu.rs | 23 ++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/scripts_menu.rs b/crates/ui-egui/src/scripts_menu.rs index bac2d1121..58be7b7b8 100644 --- a/crates/ui-egui/src/scripts_menu.rs +++ b/crates/ui-egui/src/scripts_menu.rs @@ -38,6 +38,8 @@ struct State { revision: u64, /// The listing being read on another thread. reading: Option>, + /// Another read was asked for while one was under way; [`poll`] starts it when that one ends. + pending: bool, /// A first read was asked for. started: bool, /// The window was the active one at the last [`poll`]. @@ -94,12 +96,14 @@ pub fn revision() -> u64 { state().revision } -/// Read the Scripts folder again on another thread, unless a read is under way or the session has -/// no folder (the web, and a run without preferences). `ctx` is asked to draw when the read ends. +/// Read the Scripts folder again on another thread, unless the session has no folder (the web, and +/// a run without preferences). While a read is under way, another read starts when it ends +/// ([`poll`]). `ctx` is asked to draw when the read ends. pub fn refresh(app: &VectorcraftApp, ctx: Option<&egui::Context>) { let Some(root) = app.session.scripts_dir().map(Path::to_path_buf) else { return }; let mut state = state(); if state.reading.is_some() { + state.pending = true; return; } let (tx, rx) = std::sync::mpsc::channel(); @@ -119,7 +123,7 @@ pub fn refresh(app: &VectorcraftApp, ctx: Option<&egui::Context>) { } /// Once per iteration: start a read on the first one and when the window becomes the active one, -/// and take in a read that ended. +/// take in a read that ended, and then start a read asked for while it was under way. pub fn poll(app: &VectorcraftApp, ctx: &egui::Context) { let focused = ctx.input(|i| i.focused); let start = { @@ -132,6 +136,17 @@ pub fn poll(app: &VectorcraftApp, ctx: &egui::Context) { refresh(app, Some(ctx)); } take(false); + let again = { + let mut state = state(); + let again = state.pending && state.reading.is_none(); + if again { + state.pending = false; + } + again + }; + if again { + refresh(app, Some(ctx)); + } } /// Take in the read under way: when it has ended, or with `wait` once it ends. @@ -158,6 +173,19 @@ pub(crate) fn wait() { take(true); } +/// Wait for the read under way to end and leave its listing to be taken in, so the read is under +/// way until the next [`poll`]. +#[cfg(test)] +pub(crate) fn end_untaken() { + let mut state = state(); + let Some(reading) = &state.reading else { return }; + let (tx, rx) = std::sync::mpsc::channel(); + if let Ok(listing) = reading.recv() { + let _ = tx.send(listing); + } + state.reading = Some(rx); +} + /// File › Scripts' items: the listed scripts and subfolders, a note when some may be missing or /// the folder cannot be read, then Check Script… and Other Script…. pub fn items() -> Vec { diff --git a/crates/ui-egui/src/tests/scripts_menu.rs b/crates/ui-egui/src/tests/scripts_menu.rs index 1cfb46287..f18b60f63 100644 --- a/crates/ui-egui/src/tests/scripts_menu.rs +++ b/crates/ui-egui/src/tests/scripts_menu.rs @@ -317,6 +317,29 @@ fn the_folder_is_read_at_the_first_frame_and_when_the_window_becomes_active() { let _ = std::fs::remove_dir_all(dir); } +#[test] +fn a_read_asked_for_during_another_runs_when_that_one_ends() { + let dir = folder("pending"); + let app = app(&dir); + write(&dir, "first.js", ""); + let ctx = egui::Context::default(); + let frame = || { + let mut out = ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |ui| scripts_menu::poll(&app, ui.ctx())); + out.textures_delta.clear(); + }; + frame(); + scripts_menu::wait(); + // A read that has passed the folder when a script is added, and the File menu opening then. + scripts_menu::refresh(&app, None); + scripts_menu::end_untaken(); + write(&dir, "added.js", ""); + scripts_menu::refresh(&app, None); + frame(); + scripts_menu::wait(); + assert!(shape(&scripts()).starts_with("added, first, -"), "{}", shape(&scripts())); + let _ = std::fs::remove_dir_all(dir); +} + #[test] fn an_app_without_a_scripts_folder_keeps_the_two_fixed_items() { let mut app = VectorcraftApp::new(Session::new(), crate::Services::default());