diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 9447d6653..d4fb9d42b 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -183,7 +183,69 @@ pub fn para_block(p: Paragraph) -> Arc { /// document is copy-on-write, so a block that changed was copied and has another address — /// and a block that was copied but holds the same content is compared by value. fn same_blocks(a: &Blocks, b: &Blocks) -> bool { - a.len() == b.len() && a.iter().zip(b).all(|(x, y)| Arc::ptr_eq(x, y) || **x == **y) + a.len() == b.len() && a.iter().zip(b).all(|(x, y)| Arc::ptr_eq(x, y) || same_block(x, y)) +} + +/// The same block by value. A table's cells compare their blocks by address first too: an edit in +/// one cell copies the whole table, which mustn't mean comparing every cell by value. +fn same_block(a: &Block, b: &Block) -> bool { + match (a, b) { + (Block::Table(x), Block::Table(y)) => same_table(x, y), + _ => a == b, + } +} + +/// The same table. Every field is listed, so a new one does not build until someone decides +/// whether a change to it counts. +fn same_table(a: &Table, b: &Table) -> bool { + let Table { props, grid, rows, controls } = a; + let Table { props: other_props, grid: other_grid, rows: other_rows, controls: other_controls } = b; + props == other_props + && grid == other_grid + && controls == other_controls + && rows.len() == other_rows.len() + && rows.iter().zip(other_rows).all(|(x, y)| same_row(x, y)) +} + +fn same_row(a: &Row, b: &Row) -> bool { + let Row { props, cells, controls } = a; + let Row { props: other_props, cells: other_cells, controls: other_controls } = b; + props == other_props + && controls == other_controls + && cells.len() == other_cells.len() + && cells.iter().zip(other_cells).all(|(x, y)| { + let Cell { props, blocks, controls } = x; + let Cell { props: other_props, blocks: other_blocks, controls: other_controls } = y; + props == other_props && controls == other_controls && same_blocks(blocks, other_blocks) + }) +} + +/// The same building blocks, their content compared by address first. +fn same_glossary(a: &[BuildingBlock], b: &[BuildingBlock]) -> bool { + a.len() == b.len() + && a.iter().zip(b).all(|(x, y)| { + let BuildingBlock { name, gallery, category, description, guid, style, types, behaviors, blocks } = x; + let BuildingBlock { + name: other_name, + gallery: other_gallery, + category: other_category, + description: other_description, + guid: other_guid, + style: other_style, + types: other_types, + behaviors: other_behaviors, + blocks: other_blocks, + } = y; + name == other_name + && gallery == other_gallery + && category == other_category + && description == other_description + && guid == other_guid + && style == other_style + && types == other_types + && behaviors == other_behaviors + && same_blocks(blocks, other_blocks) + }) } /// The same parts: the same keys, and each part the same. @@ -927,7 +989,7 @@ impl Document { && endnote_separators == other_endnotes && same_bytes(media, other_media) && same_bytes(passthrough, other_passthrough) - && glossary == other_glossary + && same_glossary(glossary, other_glossary) } pub fn story(&self, s: StoryRef) -> Option<&Blocks> { diff --git a/crates/doc/src/tests.rs b/crates/doc/src/tests.rs index dfef0a853..18e69c832 100644 --- a/crates/doc/src/tests.rs +++ b/crates/doc/src/tests.rs @@ -191,6 +191,20 @@ fn same_content_sees_every_change() { let mut e = d.clone(); e.para_mut(StoryRef::Body, &Path::top(0)).unwrap().touch(); assert!(d.same_content(&e), "the paragraph only changed its counter"); + // A table copied for an edit: the same until a cell's content changes. + let mut d = Document::from_text("Hello"); + d.body.insert(0, Arc::new(Block::Table(Table::new(2, 2, 200.0)))); + let mut e = d.clone(); + Arc::make_mut(&mut e.body[0]); + assert!(!Arc::ptr_eq(&d.body[0], &e.body[0])); + assert!(d.same_content(&e), "the table was only copied"); + let Block::Table(t) = Arc::make_mut(&mut e.body[0]) else { panic!("a table") }; + t.rows[1].cells[1].blocks[0] = para_block(Paragraph::with_text("x", CharProps::default())); + assert!(!d.same_content(&e), "a cell's text changed"); + // A building block was added. + let mut e = d.clone(); + e.glossary.push(BuildingBlock { name: "Sign-off".into(), ..Default::default() }); + assert!(!d.same_content(&e), "a building block was added"); } #[test]