diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 590068056..747a5e077 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -255,8 +255,8 @@ pub struct SheetApp { /// text, GridCraft's copy is the newer one and Paste uses it. clipboard_text_at_copy: Option, /// The system clipboard's text right after GridCraft's last cut (the published text, or the - /// text noted for an unpublished cut); `None` after a copy. Once the cut has been pasted, Paste - /// leaves this text alone. + /// text noted for an unpublished cut); `None` after a copy, or once the clipboard is seen + /// holding other text. Once the cut has been pasted, Paste leaves this text alone. cut_text: Option, pub started: f64, } @@ -414,10 +414,15 @@ impl SheetApp { /// /// Excel pastes a cut once: after GridCraft's cut has been pasted, the system clipboard text /// that came with it is not pasted again over the moved block; GridCraft's own (empty) - /// clipboard is pasted instead, which reports "Nothing to paste.". + /// clipboard is pasted instead, which reports "Nothing to paste.". Other text means the + /// clipboard has moved on, so the cut's text is forgotten and pastes normally later. pub fn paste_external(&mut self, text: String) { - if self.session.clipboard.is_none() && Self::same_clipboard_text(Some(&text), self.cut_text.as_deref()) { - return self.run_or_alert("edit.paste", json!({})); + if Self::same_clipboard_text(Some(&text), self.cut_text.as_deref()) { + if self.session.clipboard.is_none() { + return self.run_or_alert("edit.paste", json!({})); + } + } else { + self.cut_text = None; } let html = self.services.paste_html.as_mut().and_then(|read| read()); let params = match html { diff --git a/crates/ui-egui/tests/clipboard.rs b/crates/ui-egui/tests/clipboard.rs index 89a3d3472..c386e76f3 100644 --- a/crates/ui-egui/tests/clipboard.rs +++ b/crates/ui-egui/tests/clipboard.rs @@ -428,3 +428,36 @@ fn cmd_v_after_a_pasted_cut_does_not_paste_the_cut_text_again() { assert_eq!(value_at(&h, "D4"), Some(Value::from("Name"))); assert!(h.state().toast.is_some() || h.state().message.is_some(), "Paste reports there is nothing to paste"); } + +#[test] +fn the_cut_text_pastes_again_once_the_clipboard_has_held_other_text() { + // Once other text has been pasted, the clipboard has moved on from the cut: the same text + // copied again later (from another app, or a cell editor) is a new copy and pastes. + let mut h = harness(Services::default()); + h.state_mut().run("selection.set", json!({"cell": "A1"})).unwrap(); + h.input_mut().events.push(Event::Cut); + h.step(); + let text = h + .output() + .platform_output + .commands + .iter() + .find_map(|c| match c { + OutputCommand::CopyText(text) => Some(text.clone()), + _ => None, + }) + .expect("the cut is published"); + h.run_steps(2); + h.state_mut().run("selection.set", json!({"cell": "D4"})).unwrap(); + h.input_mut().events.push(Event::Paste(text.clone())); + h.run_steps(2); + assert_eq!(value_at(&h, "D4"), Some(Value::from("Name")), "the cut moves A1"); + h.state_mut().run("selection.set", json!({"cell": "G1"})).unwrap(); + h.input_mut().events.push(Event::Paste("other".into())); + h.run_steps(2); + assert_eq!(value_at(&h, "G1"), Some(Value::from("other"))); + h.state_mut().run("selection.set", json!({"cell": "H1"})).unwrap(); + h.input_mut().events.push(Event::Paste(text.replace('\n', "\r\n"))); + h.run_steps(2); + assert_eq!(value_at(&h, "H1"), Some(Value::from("Name")), "the same text copied afresh pastes"); +}