mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 20:26:57 -06:00
fix(tables): keep each table anchor in its own paragraph
Backspace at the start of the second of two consecutive tables merged their anchor paragraphs: Story::delete kept one table id, the other table was dropped and the paragraph held two anchors. Pasting a table next to an anchor replaced the existing table the same way, and pasting several paragraphs right before an anchor left its table unreferenced. Saving succeeded; reopening failed with "paragraph must hold exactly one anchor". fix_tables now splits a table paragraph that holds further anchors, so every anchor gets its own paragraph again. delete and insert_story tell it which table each surviving anchor belongs to (fix_tables_with), and insert_story fixes tables up once after the whole paste instead of after every run. Regression tests in doc (Backspace between tables, deleting the middle of three, paste before/after an anchor, paragraphs pasted before an anchor) and engine (text.delete and edit.paste through commands, then a file round-trip); all failed before the fix. Claude-Session: https://claude.ai/code/session_01La6SDgsmdZfgfDDmx3wiGU
This commit is contained in:
@@ -177,6 +177,43 @@ fn file_roundtrip_keeps_tables() {
|
||||
assert_eq!(st.tables.values().next().unwrap().cells[0].text.text, "A1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backspace_and_paste_between_tables_keep_both_and_the_file_reopens() {
|
||||
let n = designcraft_doc::TABLE_ANCHOR.len_utf8();
|
||||
let anchor_of = |s: &Session, sid: u64, tid: u64| s.doc().unwrap().doc.story(doc::StoryId(sid)).unwrap().table_anchor(tid).unwrap();
|
||||
let reopens_with = |s: &Session, sid: u64, ids: &[u64]| {
|
||||
let back = crate::cmd::file_from(&crate::cmd::file_bytes(&s.doc().unwrap().doc)).unwrap();
|
||||
let st = back.story(doc::StoryId(sid)).unwrap();
|
||||
assert!(ids.iter().all(|id| st.table_anchor(*id).is_some()), "{:?}", st.tables.keys());
|
||||
assert_eq!(st.tables.len(), ids.len());
|
||||
};
|
||||
// Backspace at the start of the second of two consecutive tables.
|
||||
let (mut s, sid, _) = session_with_frame();
|
||||
let t1 = s.execute("table.insert", &json!({"rows": 2, "cols": 2})).unwrap()["table"].as_u64().unwrap();
|
||||
let len = s.doc().unwrap().doc.story(doc::StoryId(sid)).unwrap().len();
|
||||
s.execute("text.select", &json!({"story": sid, "anchor": len})).unwrap();
|
||||
let t2 = s.execute("table.insert", &json!({"rows": 2, "cols": 2})).unwrap()["table"].as_u64().unwrap();
|
||||
let a2 = anchor_of(&s, sid, t2);
|
||||
s.execute("text.select", &json!({"story": sid, "anchor": a2})).unwrap();
|
||||
s.execute("text.delete", &json!({})).unwrap();
|
||||
check(&s);
|
||||
reopens_with(&s, sid, &[t1, t2]);
|
||||
// Pasting a copy of a table right after its anchor.
|
||||
let (mut s, sid, _) = session_with_frame();
|
||||
let t1 = s.execute("table.insert", &json!({"rows": 2, "cols": 2})).unwrap()["table"].as_u64().unwrap();
|
||||
let a1 = anchor_of(&s, sid, t1);
|
||||
s.execute("text.select", &json!({"story": sid, "anchor": a1, "focus": a1 + n})).unwrap();
|
||||
s.execute("edit.copy", &json!({})).unwrap();
|
||||
s.execute("text.select", &json!({"story": sid, "anchor": a1 + n})).unwrap();
|
||||
let pasted = s.execute("edit.paste", &json!({})).unwrap();
|
||||
check(&s);
|
||||
assert_eq!(pasted["pos"], a1 + 2 * n + 1);
|
||||
let st = s.doc().unwrap().doc.story(doc::StoryId(sid)).unwrap().clone();
|
||||
let copy = st.tables.keys().copied().find(|id| *id != t1).unwrap();
|
||||
assert_eq!((st.table_anchor(t1), st.table_anchor(copy)), (Some(a1), Some(a1 + n + 1)));
|
||||
reopens_with(&s, sid, &[t1, copy]);
|
||||
}
|
||||
|
||||
fn table_for(s: &Session, sid: u64, tid: u64) -> &doc::Table {
|
||||
s.doc().unwrap().doc.story(doc::StoryId(sid)).unwrap().tables.get(&tid).unwrap()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user