diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index 78d08dd..1c5e54a 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -40,8 +40,8 @@ pub fn specs() -> Vec { cmd!(noundo "edit.goToSpecial", "Go To Special…", ["Home", "Editing", "Find & Select"], None, "{kind: blanks|constants|formulas|comments|lastCell|currentRegion|visible|conditionalFormats|dataValidation|errors|numbers|text}", has_doc, go_to_special), cmd!(noundo "edit.undo", "Undo", ["Edit"], Some("Cmd+Z"), "{steps?: 1}", can_undo, undo), cmd!(noundo "edit.redo", "Redo", ["Edit"], Some("Cmd+Y"), "{steps?: 1}", can_redo, redo), - cmd!(noundo "edit.copy", "Copy", ["Home", "Clipboard"], Some("Cmd+C"), "{range?}", has_doc, copy), - cmd!(noundo "edit.cut", "Cut", ["Home", "Clipboard"], Some("Cmd+X"), "{range?}", has_doc, cut), + cmd!(noundo "edit.copy", "Copy", ["Home", "Clipboard"], Some("Cmd+C"), "{range?, html?: bool} → {range, text, html?} (html: a styled HTML table, only when requested and small enough)", has_doc, copy), + cmd!(noundo "edit.cut", "Cut", ["Home", "Clipboard"], Some("Cmd+X"), "{range?, html?: bool} → {range, text, html?} (html: a styled HTML table, only when requested and small enough)", has_doc, cut), cmd!( "edit.paste", "Paste", @@ -611,7 +611,9 @@ fn copy_impl(s: &mut Session, p: &Json, cut: bool) -> Result { range }; let text = crate::display::range_text(&d.wb, sheet, range); - let html = crate::io::range_html(&d.wb, sheet, range, &text); + // The HTML rendering is for the host clipboard (UI); it can reach 4 MB, so agents and + // scripts get it only when they ask for it. + let html = if p.get("html").and_then(Json::as_bool) == Some(true) { crate::io::range_html(&d.wb, sheet, range, &text) } else { None }; let clip = Clipboard { wb: d.wb.clone(), sheet, range, cut, doc_uid: d.uid, text: text.clone() }; s.clipboard = Some(clip); let mut result = json!({"range": range.a1(), "text": text}); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index ac1d487..6d39839 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -212,7 +212,7 @@ fn rich_clipboard_copies_selected_displayed_cells_and_styles() { s.execute("home.italic", json!({"range": "B2", "on": true})).unwrap(); s.execute("home.fontColor", json!({"range": "B2", "color": "#123456"})).unwrap(); s.execute("home.fillColor", json!({"range": "B2", "color": "#FEDCBA"})).unwrap(); - let copied = s.execute("edit.copy", json!({"range": "B2:C3"})).unwrap(); + let copied = s.execute("edit.copy", json!({"range": "B2:C3", "html": true})).unwrap(); let html = copied["html"].as_str().expect("copy publishes HTML as well as plain text"); assert_eq!(copied["text"], "12.5%\t\"&\"\"\nnext\"\n25.0%\tlast\n"); assert!(html.starts_with("x"))); + } +} + #[test] fn rich_clipboard_clips_merges_and_skips_hidden_rows() { let mut s = s(); @@ -241,7 +256,7 @@ fn rich_clipboard_clips_merges_and_skips_hidden_rows() { s.execute("home.mergeCenter", json!({"range": "B2:D5"})).unwrap(); s.execute("home.hideRows", json!({"rows": "3:3"})).unwrap(); s.execute("cell.set", json!({"cell": "E4", "input": "side"})).unwrap(); - let copied = s.execute("edit.copy", json!({"range": "B2:C4"})).unwrap(); + let copied = s.execute("edit.copy", json!({"range": "B2:C4", "html": true})).unwrap(); let html = copied["html"].as_str().expect("merged copy publishes HTML"); assert_eq!(copied["text"], "merged\t\n\t\n"); assert_eq!(html.matches("").count(), 2); @@ -251,7 +266,7 @@ fn rich_clipboard_clips_merges_and_skips_hidden_rows() { // A selection beginning inside a merge must keep its shape without copying // the original anchor's value from outside the selected rectangle. - let copied = s.execute("edit.copy", json!({"range": "C3:E5"})).unwrap(); + let copied = s.execute("edit.copy", json!({"range": "C3:E5", "html": true})).unwrap(); let html = copied["html"].as_str().expect("partially selected merge publishes HTML"); assert_eq!(html.matches("").count(), 2); assert_eq!(html.matches("