mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 01:14:29 -06:00
Merge main into feat/script-dollar-object
This commit is contained in:
@@ -33,11 +33,17 @@
|
||||
//! line and a report line; the run's report holds its own output and error, its time, its host
|
||||
//! calls by kind and its slowest commands. The rest of `$`, and the global `localize` and
|
||||
//! `toSource`, are in [`dollar`].
|
||||
//!
|
||||
//! `alert`, `confirm` and `prompt` open dialogs ([`dialogs`]): in the app the run waits for the
|
||||
//! person's answer, and a run with nobody to ask takes its answers from `script.run`'s `answers`.
|
||||
|
||||
pub mod console;
|
||||
pub mod dialogs;
|
||||
pub mod dollar;
|
||||
pub mod include;
|
||||
|
||||
pub use dialogs::{DialogAnswer, DialogKind, ScriptDialog};
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
@@ -90,7 +96,7 @@ const MAX_BRIDGE_BYTES: u64 = 4 << 30;
|
||||
/// The version `vc.api.version` gives.
|
||||
pub const API_VERSION: &str = "1.0";
|
||||
/// The features `vc.api.has` knows.
|
||||
pub const API_FEATURES: &[&str] = &["run", "document", "commands", "log", "args", "console", "dollar"];
|
||||
pub const API_FEATURES: &[&str] = &["run", "document", "commands", "log", "args", "console", "dialogs", "dollar"];
|
||||
/// The most commands a run's report lists in `slowestCommands`.
|
||||
pub const SLOWEST_COMMANDS: usize = 5;
|
||||
|
||||
@@ -151,6 +157,8 @@ pub(crate) struct Request {
|
||||
pub fuel: u64,
|
||||
/// The locale the run starts with (`$.locale`).
|
||||
pub locale: String,
|
||||
/// The answers to the dialogs of a run with nobody to ask, in order.
|
||||
pub answers: Vec<DialogAnswer>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
@@ -484,6 +492,8 @@ struct Host {
|
||||
commands: RefCell<HashMap<&'static str, (u64, u128, u128)>>,
|
||||
/// The time the run's slices took, in nanoseconds.
|
||||
run_nanos: Cell<u128>,
|
||||
/// `alert`, `confirm` and `prompt`: their answers and the list the report gives.
|
||||
dialogs: dialogs::Dialogs,
|
||||
/// The files the run reads: its includes and `$.evalFile`'s.
|
||||
files: RefCell<include::Files>,
|
||||
/// Where the lines of the script's expanded source came from.
|
||||
@@ -1104,7 +1114,7 @@ fn kind_id(kind: &RunErrorKind) -> &'static str {
|
||||
|
||||
/// `message`, with what to do about a failure that comes from a script written for another program.
|
||||
fn explained(kind: &RunErrorKind, message: &str) -> String {
|
||||
const HOST_OBJECTS: &[&str] = &["app", "alert", "confirm", "prompt", "File", "Folder", "Window", "BridgeTalk", "activeDocument", "documents"];
|
||||
const HOST_OBJECTS: &[&str] = &["app", "File", "Folder", "Window", "BridgeTalk", "activeDocument", "documents"];
|
||||
let names_host_object =
|
||||
message.strip_prefix("ReferenceError: ").is_some_and(|m| HOST_OBJECTS.iter().any(|o| m.split_whitespace().next() == Some(o)));
|
||||
if *kind == RunErrorKind::Thrown && names_host_object {
|
||||
@@ -1215,6 +1225,23 @@ impl Session {
|
||||
self.script_key.as_ref().and_then(|k| with_job(k, |j| j.host.asleep())).flatten()
|
||||
}
|
||||
|
||||
/// The dialog (`alert`, `confirm` or `prompt`) the running script waits at for the person's
|
||||
/// answer. A run asked to stop no longer waits: the next [`Session::script_pump`] ends it.
|
||||
pub fn script_dialog(&self) -> Option<ScriptDialog> {
|
||||
if self.script_stop.is_set() {
|
||||
return None;
|
||||
}
|
||||
self.script_key.as_ref().and_then(|k| with_job(k, |j| j.host.dialogs.waiting())).flatten()
|
||||
}
|
||||
|
||||
/// Gives the dialog the running script waits at its answer; the next [`Session::script_pump`]
|
||||
/// goes on from the call. → false when nothing waits, the answer is for another kind of
|
||||
/// dialog, or a prompt's text is longer than [`dialogs::MAX_PROMPT_BYTES`].
|
||||
pub fn script_answer(&mut self, answer: DialogAnswer) -> bool {
|
||||
let Some(k) = self.script_key.as_ref().filter(|_| !self.script_stop.is_set()) else { return false };
|
||||
with_job(k, |j| j.host.dialogs.answer(&mut j.realm, answer)).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// The name of the running script.
|
||||
pub fn script_name(&self) -> Option<String> {
|
||||
self.script_key.as_ref().and_then(|k| with_job(k, |j| j.name.clone()))
|
||||
@@ -1323,6 +1350,7 @@ impl Session {
|
||||
counts: Counts::default(),
|
||||
commands: RefCell::default(),
|
||||
run_nanos: Cell::new(0),
|
||||
dialogs: dialogs::Dialogs::new(self.script_frames, &realm, request.answers),
|
||||
files: RefCell::new(files),
|
||||
script_map: RefCell::default(),
|
||||
eval_maps: RefCell::default(),
|
||||
@@ -1344,6 +1372,7 @@ impl Session {
|
||||
.add_script_extensions()
|
||||
.and_then(|()| realm.define_host_object("vc", vc_object(&host, args)))
|
||||
.and_then(|()| realm.define_host_object("$", dollar_object(&host, info, &localization)))
|
||||
.and_then(|()| dialogs::install(&mut realm, &host))
|
||||
.and_then(|()| realm.start(&expanded.source))
|
||||
.map_err(|e| located(&host, realm.error_source(), &e))
|
||||
}
|
||||
@@ -1370,11 +1399,20 @@ impl Session {
|
||||
let Some(mut job) = JOBS.with(|jobs| jobs.borrow_mut().remove(&key.0)) else {
|
||||
return Some(Err(EngineError::Other(JOB_GONE.to_string())));
|
||||
};
|
||||
if (job.host.waiting.get() || job.host.asleep().is_some()) && !self.script_stop.is_set() {
|
||||
if let Some(e) = job.host.dialogs.take_failure() {
|
||||
return Some(self.script_failed(job, &e));
|
||||
}
|
||||
if (job.host.waiting.get() || job.host.asleep().is_some() || job.host.dialogs.is_waiting()) && !self.script_stop.is_set() {
|
||||
JOBS.with(|jobs| jobs.borrow_mut().insert(key.0, job));
|
||||
self.script_key = Some(key);
|
||||
return None;
|
||||
}
|
||||
// A stop while a dialog waits ends the run there; the interpreter cannot go on from the call.
|
||||
if job.realm.waits() {
|
||||
let kind = LimitKind::Interrupted;
|
||||
let e = RunError { kind: RunErrorKind::Limit(kind), message: format!("limit reached: {kind:?}"), line: 0, column: 0 };
|
||||
return Some(self.script_failed(job, &e));
|
||||
}
|
||||
if vectorcraft_doc::cancel::check().is_err() {
|
||||
self.script_stop.set();
|
||||
}
|
||||
@@ -1391,6 +1429,12 @@ impl Session {
|
||||
self.script_key = Some(key);
|
||||
None
|
||||
}
|
||||
Ok(Ok(Progress::Done(_))) if job.host.dialogs.unused() > 0 => {
|
||||
let n = job.host.dialogs.unused();
|
||||
let kind = LimitKind::Host;
|
||||
let message = format!("`answers` has {n} answer{} the script did not use", if n == 1 { "" } else { "s" });
|
||||
Some(self.script_failed(job, &RunError { kind: RunErrorKind::Limit(kind), message, line: 0, column: 0 }))
|
||||
}
|
||||
Ok(Ok(Progress::Done(_))) => {
|
||||
let r = self.script_commit(job);
|
||||
self.trim_histories();
|
||||
@@ -1400,6 +1444,7 @@ impl Session {
|
||||
}
|
||||
|
||||
fn script_failed(&mut self, job: Job, e: &RunError) -> Result<Value> {
|
||||
job.host.dialogs.stopped();
|
||||
let fuel_left = job.realm.fuel_left();
|
||||
let (e, file) = located(&job.host, job.realm.error_source(), e);
|
||||
let mut report = failure_report(&job.name, &e, file.as_deref(), &job.host, fuel_left, job.realm.limits().fuel.saturating_sub(fuel_left));
|
||||
@@ -1496,7 +1541,7 @@ impl Session {
|
||||
if let Some(result) = self.script_pump() {
|
||||
return result;
|
||||
}
|
||||
if self.script_waiting() {
|
||||
if self.script_waiting() || self.script_dialog().is_some() {
|
||||
self.script_stop.set();
|
||||
}
|
||||
// A `$.sleep` waits here, where nothing is drawn, instead of turning the loop over.
|
||||
@@ -1567,8 +1612,8 @@ fn line_to(s: &mut Session, host: &Host, entry: console::Entry, cut: bool) {
|
||||
|
||||
/// Ends the console lines of `host`'s run: the line `$.write` left unended, then for a failed run
|
||||
/// (`report["ok"]` false) its error as `name:line:column: message`, then an end line and a report
|
||||
/// line in the session's console. Adds the run's time, host calls by kind, slowest commands and
|
||||
/// console to `report`.
|
||||
/// line in the session's console. Adds the run's time, host calls by kind, slowest commands,
|
||||
/// console and dialogs to `report`.
|
||||
fn close_run(s: &mut Session, host: &Host, report: &mut Value, ending: console::Ending) {
|
||||
let mut pending = std::mem::take(&mut *host.pending.borrow_mut());
|
||||
if !pending.is_empty() {
|
||||
@@ -1602,6 +1647,7 @@ fn close_run(s: &mut Session, host: &Host, report: &mut Value, ending: console::
|
||||
o.insert("hostCallCounts".into(), host.counts.json());
|
||||
o.insert("slowestCommands".into(), Value::Array(slow_json));
|
||||
o.insert("console".into(), host.console.borrow().report());
|
||||
host.dialogs.report(o);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user