fix(edit): a refused delete keeps the object selection

Review on #4111: Delete cleared the selection before the edit ran, so a delete the engine
refuses (text used as a clipping path) left the document as it was but dropped what the user
had selected. The selection now stays until the edit applies; `ObjectSelection::prepare`
already clears it when the document changes and keeps it when an edit is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitesh
2026-10-22 23:18:01 +05:30
co-authored by Claude Opus 5.5
parent 7d6af8a508
commit caf189522f
2 changed files with 27 additions and 1 deletions
+2 -1
View File
@@ -396,8 +396,9 @@ pub(crate) fn delete_keys(ctx: &egui::Context, view: &mut DocView) {
if !ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Delete) || i.consume_key(Modifiers::NONE, Key::Backspace)) {
return;
}
// The selection stays until the edit applies: `prepare` clears it when the document changes,
// and a refused delete (text used as a clipping path) leaves it selected.
view.pending_edit = delete_edit(page, view.objects.selected.iter().map(|o| o.target).collect());
view.objects.clear();
}
/// The edit that deletes `targets` (indexes into one inventory of `page`) as one undo step.
+25
View File
@@ -4219,6 +4219,30 @@ fn an_ordinary_paragraph_drag_refusal_never_rewrites_the_text() {
assert!(h.state().toast.as_ref().unwrap().0.contains("Text used as a clipping path cannot be moved with this selection."));
}
/// A delete the engine refuses (text used as a clipping path) leaves the document as it was and
/// keeps the selection, so the user can still move or nudge what they had selected.
#[test]
fn a_refused_delete_keeps_the_object_selection() {
let content = "q BT 4 Tr /F1 12 Tf 60 320 Td (Clipping text) Tj ET Q";
let mut h = paragraph_drag_harness(paragraph_drag_fixture(content, 0, 1), 0);
let id = h.state().views[0].id;
let (generation, bytes) = {
let doc = h.state().session.get(id).unwrap();
(doc.edit_generation(), doc.bytes.clone())
};
// An area selection from empty page space around the paragraph.
let (center, _) = paragraph_drag_points(&h, "Clipping text", [0.0, 0.0]);
drag(&mut h, center - egui::vec2(60.0, 25.0), center + egui::vec2(60.0, 25.0));
h.get_by_label("1 object selected");
h.key_press(Key::Delete);
h.run_steps(4);
let doc = h.state().session.get(id).unwrap();
assert_eq!((doc.edit_generation(), &doc.bytes), (generation, &bytes), "the refused delete changed nothing");
assert!(doc.can_undo().is_none());
assert!(h.state().toast.is_some(), "the refusal is reported");
h.get_by_label("1 object selected");
}
#[test]
fn an_ordinary_paragraph_drag_cannot_reuse_an_index_after_an_edit() {
let content = "BT /F1 12 Tf 60 120 Td (Discard) Tj ET BT /F1 12 Tf 60 320 Td (Moving alpha) Tj ET BT /F1 12 Tf 60 220 Td (Keep here) Tj ET";
@@ -6002,6 +6026,7 @@ fn a_paragraph_selected_by_its_frame_deletes_with_one_key() {
press(&mut h, Modifiers::NONE, key);
assert!(blocks(&h).is_empty(), "{key:?} deletes the whole paragraph: {:?}", blocks(&h));
assert!(h.state().views[0].line_editor.is_none(), "no empty editor is left behind");
assert!(h.query_all_by_label_contains("object selected").next().is_none(), "the deleted paragraph is no longer selected");
assert_eq!(h.state().session.get(id).unwrap().text_blocks(1)[0].text, "Page 2", "the next page keeps its text");
assert_eq!(h.state().session.get(id).unwrap().info.pages.len(), 2, "Delete removed the paragraph, not a page");
assert!(h.state_mut().execute("edit.undo"));