Allow multi-column/row selection inside formulas with shift

This commit is contained in:
Cyan
2026-10-18 17:11:37 +08:00
parent e60a74e9cf
commit ef0b243cd3
3 changed files with 115 additions and 10 deletions
+28
View File
@@ -107,6 +107,23 @@ impl EditState {
self.sync_caret = true;
}
/// Adds a reference to the current point-mode union, keeping earlier references intact.
pub fn insert_ref_union(&mut self, r: &str, separator: char) {
let caret_b = self.byte_caret();
if !self.point.is_some_and(|(_, end)| end == caret_b) {
self.insert_ref(r);
return;
}
let (Some(before), Some(after)) = (self.text.get(..caret_b), self.text.get(caret_b..)) else { return };
let start = caret_b + separator.len_utf8();
let end = start + r.len();
self.text = format!("{before}{separator}{r}{after}");
self.point = Some((start, end));
self.caret = self.text.get(..end).map(|s| s.chars().count()).unwrap_or(0);
self.caret_to_end = false;
self.sync_caret = true;
}
/// Updates function autocomplete for the word before the caret.
pub fn update_autocomplete(&mut self, names: &[String]) {
self.autocomplete.clear();
@@ -349,4 +366,15 @@ mod tests {
assert_eq!(refs[3].2.a1(), "7:8");
assert_eq!(refs[3].3.as_deref(), Some("Sheet2"));
}
#[test]
fn inserting_a_union_keeps_each_reference_separately_pointed() {
let mut edit = EditState::new(0, CellRef::new(0, 0), "=SUM(".into(), true, false);
edit.insert_ref("C:C");
edit.insert_ref_union("E:E", ',');
assert_eq!(edit.text, "=SUM(C:C,E:E");
assert_eq!(formula_refs(&edit.text).iter().map(|r| r.2.a1()).collect::<Vec<_>>(), ["C:C", "E:E"]);
assert_eq!(edit.point, Some((9, 12)));
assert!(edit.can_point());
}
}
+28 -7
View File
@@ -2020,7 +2020,7 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: &
return;
}
let c = geo.col_at(sh, p.x);
if point_header(app, wb, sh, RangeRef::cols(c, c)) {
if point_header(app, wb, sh, RangeRef::cols(c, c), mods.shift, mods.ctrl) {
return;
}
if resp.drag_started()
@@ -2043,7 +2043,7 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: &
return;
}
let r = geo.row_at(sh, p.y);
if point_header(app, wb, sh, RangeRef::rows(r, r)) {
if point_header(app, wb, sh, RangeRef::rows(r, r), mods.shift, mods.ctrl) {
return;
}
let range = if mods.shift { RangeRef::rows(sel.anchor.row.min(r), sel.anchor.row.max(r)) } else { RangeRef::rows(r, r) };
@@ -2334,12 +2334,33 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: &
keyboard(app, &ctx, resp, geo, sh);
}
/// Point mode on a header: inserts the whole-column or whole-row reference instead of selecting.
fn point_header(app: &mut SheetApp, wb: &Workbook, sh: &Sheet, range: RangeRef) -> bool {
/// Point mode on a header: inserts a whole-row or whole-column reference instead of selecting.
fn point_header(app: &mut SheetApp, wb: &Workbook, sh: &Sheet, range: RangeRef, extend: bool, add: bool) -> bool {
let Some(ed) = app.editor.as_mut().filter(|ed| ed.can_point()) else { return false };
let r = range.a1();
let r = if ed.sheet != wb.active_sheet { format!("{}!{r}", gridcraft_engine::formula::quote_sheet(&sh.name)) } else { r };
ed.insert_ref(&r);
let range = if extend {
ed.point
.and_then(|point| crate::editor::formula_refs(&ed.text).into_iter().find(|(start, end, ..)| (*start, *end) == point))
.and_then(|(_, _, previous, _, _)| {
if range.is_full_cols() && previous.is_full_cols() {
Some(RangeRef::cols(previous.start.col.min(range.start.col), previous.end.col.max(range.end.col)))
} else if range.is_full_rows() && previous.is_full_rows() {
Some(RangeRef::rows(previous.start.row.min(range.start.row), previous.end.row.max(range.end.row)))
} else {
None
}
})
.unwrap_or(range)
} else {
range
};
let reference = range.a1();
let reference = if ed.sheet != wb.active_sheet { format!("{}!{reference}", gridcraft_engine::formula::quote_sheet(&sh.name)) } else { reference };
if add {
ed.insert_ref_union(&reference, ed.locale.list_separator());
} else {
ed.insert_ref(&reference);
}
ed.point_cell = Some(range.start);
ed.request_focus = true;
true
}
+59 -3
View File
@@ -35,13 +35,17 @@ fn header_center(h: &egui_kittest::Harness<'_, SheetApp>, rows: bool, index: u32
}
fn click(h: &mut egui_kittest::Harness<'_, SheetApp>, pos: Pos2) {
click_with_modifiers(h, pos, Modifiers::NONE);
}
fn click_with_modifiers(h: &mut egui_kittest::Harness<'_, SheetApp>, pos: Pos2, modifiers: Modifiers) {
h.input_mut().events.push(Event::ModifiersChanged(modifiers));
for pressed in [true, false] {
h.input_mut()
.events
.extend([Event::PointerMoved(pos), Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }]);
h.input_mut().events.extend([Event::PointerMoved(pos), Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers }]);
h.step();
}
h.run_steps(2);
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
}
#[test]
@@ -77,3 +81,55 @@ fn a_second_header_click_replaces_the_pointed_reference() {
click(&mut h, second);
assert_eq!(h.state().editor.as_ref().map(|e| e.text.as_str()), Some("=SUM(E:E"));
}
#[test]
fn shift_selects_a_contiguous_column_range_while_editing_a_formula() {
let mut h = harness();
let first = header_center(&h, false, 2);
click(&mut h, first);
let second = header_center(&h, false, 4);
click_with_modifiers(&mut h, second, Modifiers::SHIFT);
let app = h.state();
let text = app.editor.as_ref().map_or("", |ed| ed.text.as_str());
assert_eq!(text, "=SUM(C:E");
assert_eq!(gridcraft_ui_egui::editor::formula_refs(text).iter().map(|r| r.2.a1()).collect::<Vec<_>>(), ["C:E"]);
}
#[test]
fn ctrl_selects_discontiguous_columns_while_editing_a_formula() {
let mut h = harness();
let first = header_center(&h, false, 2);
click(&mut h, first);
let second = header_center(&h, false, 4);
click_with_modifiers(&mut h, second, Modifiers::CTRL);
let app = h.state();
let text = app.editor.as_ref().map_or("", |ed| ed.text.as_str());
assert_eq!(text, "=SUM(C:C,E:E");
assert_eq!(gridcraft_ui_egui::editor::formula_refs(text).iter().map(|r| r.2.a1()).collect::<Vec<_>>(), ["C:C", "E:E"]);
}
#[test]
fn shift_selects_a_contiguous_row_range_while_editing_a_formula() {
let mut h = harness();
let first = header_center(&h, true, 3);
click(&mut h, first);
let second = header_center(&h, true, 5);
click_with_modifiers(&mut h, second, Modifiers::SHIFT);
let app = h.state();
let text = app.editor.as_ref().map_or("", |ed| ed.text.as_str());
assert_eq!(text, "=SUM(4:6");
assert_eq!(gridcraft_ui_egui::editor::formula_refs(text).iter().map(|r| r.2.a1()).collect::<Vec<_>>(), ["4:6"]);
}
#[test]
fn ctrl_selects_discontiguous_rows_while_editing_a_formula() {
let mut h = harness();
let first = header_center(&h, true, 3);
click(&mut h, first);
let second = header_center(&h, true, 5);
click_with_modifiers(&mut h, second, Modifiers::CTRL);
let app = h.state();
let text = app.editor.as_ref().map_or("", |ed| ed.text.as_str());
assert_eq!(text, "=SUM(4:4,6:6");
assert_eq!(gridcraft_ui_egui::editor::formula_refs(text).iter().map(|r| r.2.a1()).collect::<Vec<_>>(), ["4:4", "6:6"]);
}