diff --git a/crates/onestore/src/op/lower.rs b/crates/onestore/src/op/lower.rs index 9be09d99c47e29467816e25799fc71d1b9e985ef..097dcb3c6bf872441b1abb84db81897778a834b3 100644 --- a/crates/onestore/src/op/lower.rs +++ b/crates/onestore/src/op/lower.rs @@ -1901,7 +1901,22 @@ impl Lowering { return Err(invalid("Text edits did not converge on the model")); } let fresh = !stored_texts.contains(&text.id); - for (range, set, clear) in format_edits(&stored.text, &text.text, fresh)? { + let edits = format_edits(&stored.text, &text.text, fresh)?; + // Math runs refuse formatting, so a style that restyled them is undone whole. + let math = |at: u32| { + stored + .text + .format_at(at + 1) + .is_ok_and(|format| format.math == Some(true)) + }; + if edits.iter().any(|(range, ..)| range.clone().any(math)) { + self.emit(PageOp::Equation { + text: stored.id, + math: text.text.clone(), + })?; + continue; + } + for (range, set, clear) in edits { self.emit(PageOp::Format { text: stored.id, range, diff --git a/crates/onestore/src/op/tests.rs b/crates/onestore/src/op/tests.rs index 255016c288532e73d3665d6435ccce910dea64bb..53e45e240d3703cfb3316e9bf5903e8a805412ff 100644 --- a/crates/onestore/src/op/tests.rs +++ b/crates/onestore/src/op/tests.rs @@ -3320,3 +3320,54 @@ fn restyle_refuses_renames_and_missing_styles() { assert!(section.page(space).unwrap() == page); } } + +/// An equation's paragraph restyled in italics, then lowered back to the page before it, as +/// undo does: the math runs a style left italic go back whole, since they refuse formatting. +#[test] +fn a_restyled_equation_lowers_back() { + let (_, source) = SOURCES.iter().find(|(name, _)| *name == "math").unwrap(); + let space = pages(source)[0]; + let arena = Arena::default(); + let mut section = Section::open(&arena, source.to_vec()).unwrap(); + let before = section.page(space).unwrap(); + let paragraph = model::lists(&before) + .into_iter() + .flat_map(|(_, _, list)| list.iter()) + .find(|paragraph| { + paragraph + .text() + .is_some_and(|text| crate::page::Math::is_equation(&text.text)) + }) + .unwrap() + .id; + let style = PageOp::Style { + paragraph, + style: id(), + definition: Definition { + kind: Kind::Style { + name: Some("Quote".into()), + next: None, + }, + format: Format { + italic: Some(true), + ..Default::default() + }, + }, + }; + let edit = |ops: Vec| Edit { + at: AT, + ops: ops.into_iter().map(|op| Op::Page { space, op }).collect(), + }; + section.apply("Author", &edit(vec![style])).unwrap(); + let styled = section.page(space).unwrap(); + section + .apply("Author", &edit(lower_page(&styled, &before).unwrap())) + .unwrap(); + let after = section.page(space).unwrap(); + assert_eq!( + normalize(&after), + normalize(&before), + "{}", + first_difference(&normalize(&after), &normalize(&before)) + ); +}