Merge pull request #1925 from DrM7md:nux/insert-delete-whole-lines

This commit is contained in:
Brandon Thomas
2026-10-19 12:32:51 -04:00
2 changed files with 104 additions and 8 deletions
+39 -8
View File
@@ -2452,14 +2452,25 @@ pub fn shortcut(app: &mut SheetApp, key: Key, m: Modifiers) {
Key::Num9 => Some(("home.hideRows", json!({}))),
Key::Num0 if shift => Some(("home.unhideColumns", json!({}))),
Key::Num0 => Some(("home.hideColumns", json!({}))),
Key::Minus => {
app.open_dialog("deleteCells", json!({}));
None
}
Key::Plus | Key::Equals if shift => {
app.open_dialog("insertCells", json!({}));
None
}
// As in Excel: with whole rows or whole columns selected, Ctrl+Shift+= / Ctrl+- insert or
// delete them at once; any other selection asks which way to shift.
Key::Minus => match whole_lines(app) {
Some(true) => Some(("home.deleteRows", json!({}))),
Some(false) => Some(("home.deleteColumns", json!({}))),
None => {
app.open_dialog("deleteCells", json!({}));
None
}
},
// Insert works on one area at a time, so several areas still go through the dialog.
Key::Plus | Key::Equals if shift => match whole_lines(app).filter(|_| single_area(app)) {
Some(true) => Some(("home.insertRows", json!({}))),
Some(false) => Some(("home.insertColumns", json!({}))),
None => {
app.open_dialog("insertCells", json!({}));
None
}
},
Key::Semicolon => {
// Insert today's date.
let text = app
@@ -2514,3 +2525,23 @@ mod checkbox_on_tests {
assert!(!checkbox_on(false, true));
}
}
/// Whether the selection is a single area.
fn single_area(app: &SheetApp) -> bool {
app.session.active().is_some_and(|d| d.selection.ranges.len() == 1)
}
/// `Some(true)` when every selected area is whole rows, `Some(false)` when every one is whole
/// columns, `None` otherwise (including the whole sheet).
fn whole_lines(app: &SheetApp) -> Option<bool> {
let sel = &app.session.active()?.selection;
let rows = sel.ranges.iter().all(|r| r.is_full_rows() && !r.is_full_cols());
let cols = sel.ranges.iter().all(|r| r.is_full_cols() && !r.is_full_rows());
if rows {
Some(true)
} else if cols {
Some(false)
} else {
None
}
}
@@ -0,0 +1,65 @@
//! Ctrl+Shift+= and Ctrl+- on whole rows or columns insert or delete them at once, as in Excel;
//! any other selection opens the Insert or Delete dialog.
use egui::{Key, Modifiers};
use gridcraft_engine::Session;
use gridcraft_engine::core::{CellRef, Value};
use gridcraft_ui_egui::SheetApp;
use serde_json::json;
type Harness = egui_kittest::Harness<'static, SheetApp>;
fn harness(selection: &str) -> Harness {
let mut session = Session::new();
session.new_workbook();
session.execute("range.setValues", json!({"range": "A1", "values": [["a1", "b1"], ["a2", "b2"], ["a3", "b3"]]})).unwrap();
session.execute("selection.set", json!({"range": selection})).unwrap();
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SheetApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
SheetApp::new(session, Default::default()),
);
h.run_steps(4);
h
}
fn value(h: &Harness, a1: &str) -> Value {
h.state().session.active().unwrap().wb.active().unwrap().value(CellRef::parse(a1).unwrap())
}
fn press(h: &mut Harness, key: Key, shift: bool) {
let modifiers = Modifiers { shift, ..Modifiers::COMMAND };
h.key_press_modifiers(modifiers, key);
h.run_steps(3);
}
#[test]
fn whole_rows_are_inserted_and_deleted_at_once() {
let mut h = harness("2:2");
press(&mut h, Key::Equals, true);
assert!(h.state().dialog.is_none(), "no dialog for whole rows");
assert!(value(&h, "A2").is_empty(), "a row was inserted above row 2");
assert_eq!(value(&h, "A3"), Value::from("a2"));
press(&mut h, Key::Minus, false);
assert_eq!(value(&h, "A2"), Value::from("a2"), "and deleted again");
}
#[test]
fn whole_columns_are_inserted_and_deleted_at_once() {
let mut h = harness("A:A");
press(&mut h, Key::Equals, true);
assert!(h.state().dialog.is_none(), "no dialog for whole columns");
assert_eq!(value(&h, "B1"), Value::from("a1"));
press(&mut h, Key::Minus, false);
assert_eq!(value(&h, "A1"), Value::from("a1"));
}
#[test]
fn other_selections_still_ask_which_way_to_shift() {
let mut h = harness("A2:B2");
press(&mut h, Key::Minus, false);
assert_eq!(h.state().dialog.as_ref().map(|d| d.name().to_string()).as_deref(), Some("deleteCells"));
}