From 8ee9bc144bcd8226babbb423736c2341dd596546 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 27 Oct 2026 11:06:19 -0700 Subject: [PATCH] Keep failed scripts and selected panels out of recent script history --- crates/engine/src/cmd/file_more.rs | 17 +++++++------ crates/engine/src/cmd/scripts.rs | 40 +++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/crates/engine/src/cmd/file_more.rs b/crates/engine/src/cmd/file_more.rs index cee7e3236..5264ba7a8 100644 --- a/crates/engine/src/cmd/file_more.rs +++ b/crates/engine/src/cmd/file_more.rs @@ -450,9 +450,6 @@ fn run_script(s: &mut Session, p: &Value) -> Result { && p.get("steps").is_none() { let name = name.to_string(); - if super::scripts::scripts(s).iter().any(|e| e.name == name && !e.panel) { - remember_script(s, crate::prefs::RecentScript::Name(name.clone())); - } return super::scripts::run_named(s, &name); } if let Some(path) = str_p(p, "path") @@ -464,18 +461,19 @@ fn run_script(s: &mut Session, p: &Value) -> Result { } let bytes = s.services.read_file(path).map_err(|e| EngineError::Other(format!("cannot read {path}: {e}")))?; let full = s.services.full_path(path); - remember_script(s, crate::prefs::RecentScript::Path(full)); let code = String::from_utf8_lossy(&bytes).to_string(); let name = std::path::Path::new(path).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| path.to_string()); let out = run_js(s, &code, path, false)?; - return report_js(s, &name, out); + let result = report_js(s, &name, out)?; + remember_script(s, crate::prefs::RecentScript::Path(full)); + return Ok(result); } + let mut script_path = None; let steps: Vec = match (p.get("steps"), str_p(p, "path")) { (Some(Value::Array(a)), _) => a.clone(), (_, Some(path)) => { let bytes = s.services.read_file(path).map_err(|e| EngineError::Other(format!("cannot read {path}: {e}")))?; - let full = s.services.full_path(path); - remember_script(s, crate::prefs::RecentScript::Path(full)); + script_path = Some(s.services.full_path(path)); let text = String::from_utf8_lossy(&bytes).to_string(); match serde_json::from_str::(&text) { Ok(Value::Array(a)) => a, @@ -508,11 +506,14 @@ fn run_script(s: &mut Session, p: &Value) -> Result { let r = r.map_err(|e| EngineError::Other(format!("step {} ({id}): {e}", i + 1)))?; results.push(r); } + if let Some(path) = script_path { + remember_script(s, crate::prefs::RecentScript::Path(path)); + } Ok(json!({"steps": results.len(), "results": results})) } /// Remember a script that runs for File ▸ Scripts ▸ Recent Script Files. -fn remember_script(s: &mut Session, script: crate::prefs::RecentScript) { +pub(super) fn remember_script(s: &mut Session, script: crate::prefs::RecentScript) { s.prefs.push_recent_script(script); s.save_prefs(); s.prefs_revision += 1; diff --git a/crates/engine/src/cmd/scripts.rs b/crates/engine/src/cmd/scripts.rs index 9c109a0c0..58da7bd49 100644 --- a/crates/engine/src/cmd/scripts.rs +++ b/crates/engine/src/cmd/scripts.rs @@ -241,7 +241,9 @@ pub(crate) fn run_named(s: &mut Session, name: &str) -> Result { if panel { return open_panel(s, &json!({"name": name})); } - super::file_more::run_js_named(s, &code, name) + let result = super::file_more::run_js_named(s, &code, name)?; + super::file_more::remember_script(s, crate::prefs::RecentScript::Name(name.to_string())); + Ok(result) } /// The code a ScriptUI panel runs with: `this` is a dockable Panel titled after the script. @@ -603,6 +605,42 @@ mod tests { let _ = std::fs::remove_dir_all(&root); } + #[test] + fn recent_script_history_uses_the_selected_script() { + let (mut s, root) = session_in_tmp("recent-panel-collision"); + let name = "Rename Layers.jsx"; + std::fs::write(root.join("settings").join(PANELS_DIR).join(name), "panel").unwrap(); + assert!(super::scripts(&s).iter().any(|entry| entry.name == name && !entry.panel)); + assert!(script_code(&s, name, None).unwrap().0); + s.execute("file.runScript", json!({"name": name})).unwrap(); + assert!(s.prefs.recent_scripts.is_empty(), "the selected panel must not enter recent script history"); + let _ = std::fs::remove_dir_all(root); + } + + #[test] + fn recent_script_history_keeps_successful_entries_when_scripts_fail() { + fn fail_or_echo(_: &mut Session, request: &ScriptRequest) -> Value { + if request.code == "fail" { json!({"error": {"message": "script failed"}}) } else { json!({"ran": request.code}) } + } + let (mut s, root) = session_in_tmp("recent-failed"); + s.script = Some(fail_or_echo); + let good = root.join("good.jsonl"); + std::fs::write(&good, "[]").unwrap(); + s.execute("file.runScript", json!({"path": good})).unwrap(); + let remembered = s.prefs.recent_scripts.clone(); + for (name, code) in [("parse.jsonl", "{"), ("step.jsonl", r#"{"command":"does.not.exist"}"#), ("missing-command.jsonl", "{}"), ("runtime.jsx", "fail")] + { + let path = root.join(name); + std::fs::write(&path, code).unwrap(); + assert!(s.execute("file.runScript", json!({"path": path})).is_err(), "{name}"); + assert_eq!(s.prefs.recent_scripts, remembered, "{name}"); + } + std::fs::write(root.join("settings").join(SCRIPTS_DIR).join("Failed.jsx"), "fail").unwrap(); + assert!(s.execute("file.runScript", json!({"name": "Failed.jsx"})).is_err()); + assert_eq!(s.prefs.recent_scripts, remembered); + let _ = std::fs::remove_dir_all(root); + } + /// Reads relative paths from a working directory that a test can change. struct InFolder(std::sync::Mutex);