This commit is contained in:
Brandon Thomas committed 2026-10-14 12:39:58 -04:00
commit 6ceb57d4f5
8 files changed
+2280 -540

No files matched your search

File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -15,7 +15,8 @@ mod editorial;
mod endnotes;
mod export;
mod file;
mod find;
pub(crate) mod find;
pub use find::same_find_domain;
mod fonts;
mod guides;
mod index;
+2 -1
View File
@@ -365,7 +365,8 @@ fn select_at(s: &mut Session, p: &Value, unit: &str) -> Result<Value> {
s.typing_format = None;
let st = s.doc_mut()?;
st.selection = Selection::text(TextSel { story: sid, anchor: a, focus: e, frame: Some(frame), cell });
st.revision += 1;
// Explicit reselection invalidates the Find scope even when the range is unchanged.
st.revision = st.revision.wrapping_add(1);
Ok(json!({"story": sid.0, "anchor": a, "focus": e}))
}
+20
View File
@@ -260,6 +260,7 @@ pub struct Session {
pub cache: Arc<Cache>,
pub(crate) tool: Box<dyn designcraft_tools::Tool>,
pub journal: Vec<(String, Value)>,
pub(crate) find_context: Option<cmd::find::FindContext>,
pub clipboard: Option<Arc<Document>>,
/// Crash-recovery folder (the app sets it; saving or closing a document removes its entry).
pub recovery_dir: Option<std::path::PathBuf>,
@@ -302,6 +303,7 @@ impl Session {
cache: Arc::new(Cache::new()),
tool: designcraft_tools::create("selection"),
journal: vec![],
find_context: None,
clipboard: None,
recovery_dir: None,
text_clipboard: None,
@@ -361,13 +363,26 @@ impl Session {
pub fn doc_mut(&mut self) -> Result<&mut DocState> {
self.active_mut().ok_or(EngineError::NoDocument)
}
/// Release the transient Find/Change domain when its caller starts or closes a search.
pub fn reset_find_scope(&mut self) {
self.find_context = None;
}
fn discard_stale_find_scope(&mut self) {
if self.find_context.as_ref().is_some_and(|c| self.active().is_none_or(|st| !c.is_current(st))) {
self.reset_find_scope();
}
}
pub fn set_active(&mut self, i: usize) {
if i < self.docs.len() && self.active != Some(i) {
self.reset_find_scope();
self.stop_data_preview();
self.active = Some(i);
}
}
pub fn add_document(&mut self, d: DocState) -> usize {
self.reset_find_scope();
self.stop_data_preview();
self.docs.push(d);
self.active = Some(self.docs.len() - 1);
@@ -391,6 +406,7 @@ impl Session {
recovery::discard_document(self, i, recovery_dir.as_deref());
}
if i < self.docs.len() {
self.reset_find_scope();
self.docs.remove(i);
self.active = if self.docs.is_empty() { None } else { Some(i.min(self.docs.len() - 1)) };
}
@@ -421,6 +437,8 @@ impl Session {
if id != "data.preview" && (spec.undoable || spec.journal) {
self.stop_data_preview();
}
// Preview restoration can replace the document before a Find handler resolves bounds.
self.discard_stale_find_scope();
let before = self.active().map(|d| (d.uid, d.doc.clone()));
let r = (spec.run)(self, params)?;
// Undo, redo or deleting layers can take the active layer away: new objects would land on
@@ -458,6 +476,7 @@ impl Session {
if spec.journal {
self.journal.push((id.to_string(), params.clone()));
}
self.discard_stale_find_scope();
Ok(r)
}
@@ -545,6 +564,7 @@ impl Session {
st.doc = Arc::new(doc);
st.selection = sel;
st.revision += 1;
self.reset_find_scope();
Ok(r)
}
}
+7 -1
View File
@@ -310,13 +310,19 @@ fn handle_request(app: &mut DesignApp, ctx: &egui::Context, req: &ControlRequest
"ui.dialog.set" => match app.ui.dialog.as_mut() {
Some(d) => {
let Some(f) = s("field") else { return err("missing `field`") };
d.fields.insert(f.to_string(), p.get("value").cloned().unwrap_or(Value::Null));
let value = p.get("value").cloned().unwrap_or(Value::Null);
if d.set_field(f, value) {
app.session.reset_find_scope();
}
ok(serde_json::to_value(&*d).unwrap_or_default())
}
None => err("no dialog open"),
},
"ui.dialog.open" => {
let Some(id) = s("id") else { return err("missing `id`") };
if id == "findChange" || app.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange") {
app.session.reset_find_scope();
}
let d = crate::dialogs::Dialog::new(id, p.get("fields").cloned().unwrap_or(Value::Null));
let v = serde_json::to_value(&d).unwrap_or_default();
app.ui.dialog = Some(d);
File diff suppressed because it is too large. Load diff
+9 -1
View File
@@ -739,6 +739,7 @@ impl DesignApp {
pub fn after_engine(&mut self) {
let reqs = std::mem::take(&mut self.session.ui_requests);
for r in reqs {
let was_find = self.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange");
match r {
UiRequest::Dialog { id, params } => self.ui.dialog = Some(dialogs::Dialog::new(&id, params)),
UiRequest::View { params } => canvas::apply_view_request(self, &params),
@@ -747,6 +748,10 @@ impl DesignApp {
let _ = self.pick_and_open(&purpose);
}
}
// Release only when the request actually replaces Find, including a native PDF picker.
if was_find && !self.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange") {
self.session.reset_find_scope();
}
}
// New documents get a fitted view.
if let Some(st) = self.session.active() {
@@ -1106,6 +1111,9 @@ impl DesignApp {
let r = self.run(cmd, params);
if let Err(e) = &r {
let title = if cmd == "file.place" { "Can't Place the File" } else { "Can't Open the File" };
if self.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange") {
self.session.reset_find_scope();
}
self.ui.dialog = Some(dialogs::Dialog::new("alert", json!({"title": title, "file": file, "message": e})));
} else if let Ok(result) = &r {
let warnings: Vec<&str> = result
@@ -2248,7 +2256,7 @@ mod browser_import_tests {
result.push_str(&format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objects.len() + 1));
result.into_bytes()
}
fn pdf() -> Vec<u8> {
pub(super) fn pdf() -> Vec<u8> {
pdf_pages(3)
}
+18
View File
@@ -758,6 +758,15 @@ pub fn ui_label(id: &str) -> Option<(&'static str, Option<&'static str>)> {
/// Run a UI command; `None` if `id` isn't one.
pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
let was_find = app.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange");
let result = run_ui_inner(app, id, p);
if was_find && app.ui.dialog.as_ref().is_none_or(|d| d.id != "findChange") {
app.session.reset_find_scope();
}
result
}
fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
if let Some(result) = crate::panel_docking::command(app, id, p) {
return Some(result);
}
@@ -963,6 +972,7 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
}
}
"app.findChange" => {
app.session.reset_find_scope();
app.ui.dialog = Some(crate::dialogs::Dialog::new("findChange", json!({})));
Ok(Value::Null)
}
@@ -1890,6 +1900,14 @@ pub fn close_document(app: &mut DesignApp, index: Option<usize>) {
/// A menu item was chosen. An engine command whose label ends in "…" and that takes parameters
/// opens a dialog built from its parameter documentation (see [`crate::dialogs::command_fields`]).
pub fn activate(app: &mut DesignApp, id: &str, params: &Value) {
let was_find = app.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange");
activate_inner(app, id, params);
if was_find && app.ui.dialog.as_ref().is_none_or(|d| d.id != "findChange") {
app.session.reset_find_scope();
}
}
fn activate_inner(app: &mut DesignApp, id: &str, params: &Value) {
if params.is_null() && id == "file.close" {
close_document(app, None);
return;