diff --git a/ROADMAP.md b/ROADMAP.md index 7cecac45d..ce77a70b7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -137,6 +137,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in |---|---| | 2026-10-24 | Go To lists the named ranges of every sheet (`Sheet2!Name` for a name scoped to another sheet; hidden names, constants and formulas left out) and the references entered before, a double-click goes to an entry, and the Reference box takes several names or references separated by commas (`Price, Type`, `B14:C22,F19:G30`) | | 2026-10-24 | Status bar: a right-click opens a menu that sets which selection statistics show (Average, Count and Sum by default; Numerical Count, Minimum and Maximum are new), kept with the preferences (`view.statusBar`) | +| 2026-10-24 | Ctrl+Shift+U (Control+Shift+U on a Mac) expands and collapses the formula bar, on the grid and while editing (`view.expandFormulaBar`); on Windows and Linux it used to underline | | 2026-10-23 | Accounting negatives in text: `$(1,234)`, `$ (5)` and `(5)%` read as negative numbers when typed, coerced by arithmetic or passed to VALUE, like Excel 365; `(-5)`, `((5))`, `$5%` and dates or times in parentheses stay text | | 2026-10-23 | Calc: `INDEX(A1:C3,2)` (one index on a reference of several rows and columns) is `#REF!` as in Excel, instead of the whole row; `INDEX(A1:C3,2,)` still gives the row | | 2026-10-22 | Formula hardening: digits too many for a finite number are text, so `"1 <400 digits>/<400 digits>"` no longer coerces to NaN; a NaN column index in VLOOKUP, HLOOKUP and the D-functions is `#VALUE!` instead of an index underflow; REGEXREPLACE stops building a result as soon as it can only be too long (a worst case went from 1 GB to 7 MB) (#2163, #2162) | diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index 11d535ac4..8e29bccb4 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -230,6 +230,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) - for (id, label, params) in [ ("ui.ribbonTab", "Ribbon Tab", ""), ("view.collapseRibbon", "Collapse Ribbon", ""), + ("view.expandFormulaBar", "Expand Formula Bar", "{on?: bool} (toggles without it) → {on}"), ("view.darkMode", "Dark Mode", ""), ("view.theme", "Display Theme", ""), ("view.animations", "Animations", "{on?: bool} (toggles without it) → {on}"), diff --git a/crates/ui-egui/src/formula_bar.rs b/crates/ui-egui/src/formula_bar.rs index 5ec8c4a87..708b006d6 100644 --- a/crates/ui-egui/src/formula_bar.rs +++ b/crates/ui-egui/src/formula_bar.rs @@ -295,6 +295,10 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font: state.store(ui.ctx(), id); } } + // Ctrl+Shift+U (Control+Shift+U on a Mac) expands or collapses the formula bar while editing too. + if ui.input_mut(|i| i.consume_key(egui::Modifiers::CTRL | egui::Modifiers::SHIFT, Key::U)) { + app.run_or_alert("view.expandFormulaBar", json!({})); + } } // The text widget. let refs = formula_refs(&ed.text); diff --git a/crates/ui-egui/src/keymap.rs b/crates/ui-egui/src/keymap.rs index 424ab0355..54058dd71 100644 --- a/crates/ui-egui/src/keymap.rs +++ b/crates/ui-egui/src/keymap.rs @@ -103,6 +103,8 @@ pub fn lookup(key: Key, physical: Option, m: Modifiers, mac: bool) -> Optio L if (cmd || ctrl) && s => run("data.filter"), L if cmd && mac => run("home.alignLeft"), L if ctrl && mac => Shortcut::Dialog("defineName", json!({})), + // Ctrl+Shift+U (Control+Shift+U in Excel for Mac) expands or collapses the formula bar. + U if ctrl && s => run("view.expandFormulaBar"), U if ctrl && mac && !s => Shortcut::Edit, L if cmd => run("insert.table"), E if cmd && mac && !s => run("home.alignCenter"), diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 9c5b560b4..f4d9422a1 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -632,6 +632,10 @@ impl SheetApp { self.ui.ribbon_collapsed = !self.ui.ribbon_collapsed; Ok(Json::Null) } + "view.expandFormulaBar" => { + self.ui.formula_bar_expanded = p.get("on").and_then(Json::as_bool).unwrap_or(!self.ui.formula_bar_expanded); + Ok(json!({"on": self.ui.formula_bar_expanded})) + } "view.darkMode" => { let current = if self.ui.system_theme { self.effective_dark } else { self.ui.dark }; self.ui.dark = p.get("on").and_then(Json::as_bool).unwrap_or(!current); diff --git a/crates/ui-egui/tests/it/formula_bar_expand.rs b/crates/ui-egui/tests/it/formula_bar_expand.rs new file mode 100644 index 000000000..95b4368e5 --- /dev/null +++ b/crates/ui-egui/tests/it/formula_bar_expand.rs @@ -0,0 +1,95 @@ +//! Ctrl+Shift+U (Control+Shift+U in Excel for Mac) expands and collapses the formula bar. + +use egui::{Event, Key, Modifiers}; +use gridcraft_engine::Session; +use gridcraft_ui_egui::SheetApp; +use gridcraft_ui_egui::keymap::{Shortcut, lookup}; +use serde_json::json; + +type Harness = egui_kittest::Harness<'static, SheetApp>; + +/// Ctrl+Shift on Windows and Linux sets `ctrl`, `command` and `shift`. +const CTRL_SHIFT: Modifiers = Modifiers { alt: false, ctrl: true, shift: true, mac_cmd: false, command: true }; +/// Control+Shift on a Mac: `ctrl` without `command`. +const MAC_CONTROL_SHIFT: Modifiers = Modifiers { alt: false, ctrl: true, shift: true, mac_cmd: false, command: false }; + +fn harness() -> Harness { + let mut session = Session::new(); + session.new_workbook(); + session.execute("cell.set", json!({"cell": "A1", "input": "text"})).unwrap(); + session.execute("selection.set", json!({"range": "A1"})).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 ctrl_shift_u(h: &mut Harness, m: Modifiers) { + h.input_mut().events.push(Event::Key { key: Key::U, physical_key: Some(Key::U), pressed: true, repeat: false, modifiers: m }); + h.input_mut().events.push(Event::Key { key: Key::U, physical_key: Some(Key::U), pressed: false, repeat: false, modifiers: m }); + h.run_steps(2); +} + +fn expanded(h: &Harness) -> bool { + h.state().ui.formula_bar_expanded +} + +fn underlined(h: &Harness) -> bool { + h.state().session.journal.iter().any(|(id, _)| id == "home.underline") +} + +#[test] +fn ctrl_shift_u_on_the_grid_expands_and_collapses_the_formula_bar() { + let mut h = harness(); + assert!(!expanded(&h)); + ctrl_shift_u(&mut h, CTRL_SHIFT); + assert!(expanded(&h), "the first Ctrl+Shift+U expands the formula bar"); + ctrl_shift_u(&mut h, CTRL_SHIFT); + assert!(!expanded(&h), "the second Ctrl+Shift+U collapses it"); + assert!(!underlined(&h), "Ctrl+Shift+U is not Underline"); +} + +#[test] +fn control_shift_u_expands_the_formula_bar_in_excel_for_mac() { + let mut h = harness(); + h.ctx.set_os(egui::os::OperatingSystem::Mac); + h.run_steps(2); + ctrl_shift_u(&mut h, MAC_CONTROL_SHIFT); + assert!(expanded(&h)); + assert!(h.state().editor.is_none(), "Control+Shift+U does not start editing as Control+U does"); + let expand = Some(Shortcut::Run("view.expandFormulaBar", json!({}))); + assert_eq!(lookup(Key::U, Some(Key::U), MAC_CONTROL_SHIFT, true), expand); + assert_eq!(lookup(Key::U, Some(Key::U), CTRL_SHIFT, false), expand); + // Control+U on a Mac still edits the active cell. + assert_eq!(lookup(Key::U, Some(Key::U), Modifiers::CTRL, true), Some(Shortcut::Edit)); +} + +#[test] +fn ctrl_shift_u_while_editing_keeps_the_entry() { + let mut h = harness(); + h.state_mut().begin_edit(Some("draft".into()), false); + h.run_steps(2); + ctrl_shift_u(&mut h, CTRL_SHIFT); + assert!(expanded(&h), "Ctrl+Shift+U works in the cell editor too"); + let ed = h.state().editor.as_ref().expect("the edit stays open"); + assert_eq!(ed.text, "draft"); + ctrl_shift_u(&mut h, CTRL_SHIFT); + assert!(!expanded(&h)); + assert!(h.state().editor.is_some()); +} + +#[test] +fn expand_formula_bar_command_sets_or_toggles_the_state() { + let mut h = harness(); + assert_eq!(h.state_mut().run("view.expandFormulaBar", json!({"on": true})), Ok(json!({"on": true}))); + assert!(expanded(&h)); + assert_eq!(h.state_mut().run("view.expandFormulaBar", json!({"on": true})), Ok(json!({"on": true}))); + assert_eq!(h.state_mut().run("view.expandFormulaBar", json!({})), Ok(json!({"on": false}))); + assert!(!expanded(&h)); +} diff --git a/crates/ui-egui/tests/it/main.rs b/crates/ui-egui/tests/it/main.rs index ce62b2b36..85c58a764 100644 --- a/crates/ui-egui/tests/it/main.rs +++ b/crates/ui-egui/tests/it/main.rs @@ -35,6 +35,7 @@ mod find_dialog_focus; mod floating_pictures; mod font_size_box; mod format_painter; +mod formula_bar_expand; mod formula_locale; mod formula_point_headers; mod formula_popup; diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 7ca79f346..1449ac28d 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (Go To lists the named ranges of every sheet and the references entered before, and takes several names or references separated by commas; F3 Paste Name dialog with Paste List; right-clicking the status bar opens a menu of its statistics, with Numerical Count, Minimum and Maximum added; New Formatting Rule's formula box takes references clicked or dragged on the grid, #2348; Page Up and Ctrl+Up into frozen rows, Ctrl+Left and Alt+Page Up into frozen columns bring the scrolled area back; Auto Fill Options button after a left-drag of the fill handle (#1403); Show Outline Symbols, Ctrl+8; a too-wide number in a repeat-fill format such as Accounting shows #### instead of losing its leading digits; Esc and Enter close a data validation alert over the cell editor; Ctrl+Enter fill gives each cell the automatic number format typing gives it; keytip letters typed with Alt held, #1846; Number Stored as Text indicator and Convert to Number) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (Go To lists the named ranges of every sheet and the references entered before, and takes several names or references separated by commas; F3 Paste Name dialog with Paste List; right-clicking the status bar opens a menu of its statistics, with Numerical Count, Minimum and Maximum added; Ctrl+Shift+U expands and collapses the formula bar; New Formatting Rule's formula box takes references clicked or dragged on the grid, #2348; Page Up and Ctrl+Up into frozen rows, Ctrl+Left and Alt+Page Up into frozen columns bring the scrolled area back; Auto Fill Options button after a left-drag of the fill handle (#1403); Show Outline Symbols, Ctrl+8; a too-wide number in a repeat-fill format such as Accounting shows #### instead of losing its leading digits; Esc and Enter close a data validation alert over the cell editor; Ctrl+Enter fill gives each cell the automatic number format typing gives it; keytip letters typed with Alt held, #1846; Number Stored as Text indicator and Convert to Number) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md). **UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured @@ -137,8 +137,10 @@ otherwise. Number-format and border keys match the physical key (Ctrl+Shift+4, w | F3 | not checked | Paste Name dialog, only when the workbook has names: OK puts the name at the caret of the formula being edited (or starts `=Name`), Paste List writes each name and what it refers to from the active cell (`formulas.pasteName`) | **added** | | F1, F4 (repeat), F6 / Shift+F6, F8 / Shift+F8, F11, End mode | same | help, Repeat, pane cycling, extend/add mode, chart sheet | missing (no such feature yet) | | Ctrl+Q, Ctrl+A after a function name, Ctrl+Shift+U, Ctrl+Alt+P | same | Quick Analysis, Function Arguments, formula bar height, tooltips | missing | +| F1, F3, F4 (repeat), F6 / Shift+F6, F8 / Shift+F8, F11, End mode | same | help, Paste Name, Repeat, pane cycling, extend/add mode, chart sheet | missing (no such feature yet) | +| Ctrl+Q, Ctrl+A after a function name, Ctrl+Alt+P | same | Quick Analysis, Function Arguments, tooltips | missing | | Ctrl+Shift+A after a function name | Control+Shift+A | argument names: `=SUMIF(` becomes `=SUMIF(range,criteria,sum_range)`, the `(` added when missing; one Undo takes them back | **added** | -| Ctrl+6, Ctrl+Q, Ctrl+A after a function name, Ctrl+Shift+U, Ctrl+Alt+P | same | objects, Quick Analysis, Function Arguments, formula bar height, tooltips | missing | +| Ctrl+Shift+U | Control+Shift+U | expand or collapse the formula bar (`view.expandFormulaBar`), on the grid and while editing; on Windows and Linux it no longer underlines | **added** | | Ctrl+Shift+Page Up / Down, Ctrl+Alt+5, Ctrl+Shift+F1, Ctrl+F5–F10, Alt+F8 / F11 / F12 | same | group sheets, shape cycling, full screen, window keys, macros, VBA, Power Query | missing | | — | Cmd+= (calculate), Cmd+T (references while editing; F4 works), Shift+F7, Cmd+Shift+L | Mac-only extras | missing (Cmd+= is egui's zoom key; no thesaurus or Modify Cell Style) | @@ -185,6 +187,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12. |---|---|---| | 2026-10-24 | minor | Go To lists the named ranges on every sheet, as Excel's does ("The Go to popup window shows named ranges on every worksheet in your workbook"): a name scoped to another sheet appears as `Sheet2!Name`, and hidden names, constants and formulas are left out. It keeps the references entered (newest first, ten at most), and a double-click on an entry goes there. The Reference box and `edit.goTo` take several names or references separated by commas (`Price, Type`, `B14:C22,F19:G30,H21:H29`) and select them all; `edit.goTo {list: true}` returns the list | | 2026-10-24 | minor | Right-clicking the status bar opens a menu of its statistics, as in Excel ("If you want to customize the status bar, right-click it, and then click the options that you want"): Average, Count and Sum are on by default, and Numerical Count, Minimum and Maximum are new and off by default. The choice is a preference (`view.statusBar`), kept in `prefs.json` | +| 2026-10-24 | minor | Ctrl+Shift+U (Control+Shift+U on a Mac) expands or collapses the formula bar, as in Excel ("Expand or collapse the formula bar. Ctrl+Shift+U"), on the grid and in the cell editor or formula bar while editing. On Windows and Linux it used to underline the selection, as Ctrl+U does. The command is `view.expandFormulaBar` (`{on?}`, toggles without it) | | 2026-10-23 | minor | F3, Formulas › Use in Formula › Paste Names… and Alt+M, S, P open the Paste Name dialog (F3 only when the workbook has names). It lists the workbook's names and the active sheet's own in alphabetical order, hidden names left out; OK or a double-click puts the selected name at the caret of the formula being edited, in place of selected text, or starts `=Name` in the active cell (`formulas.pasteName` `{name}`), and Paste List writes each name and the text of what it refers to in two columns from the active cell (`{list: true}`). While a dialog is open over the edit, the cell editor takes no keys, and Cancel leaves the selected text selected. The Use in Formula menu lists the same names and inserts at the caret too (`formulas.useInFormula`); it used to list hidden names and other sheets' names, the menu added the name at the end of the text, and the command replaced the text being edited | | 2026-10-23 | minor | Conditional Formatting ▸ New Rule, formula: clicking a cell on the grid writes its absolute reference at the formula box's caret (`=$B$2`, an empty box starts with `=`), a drag writes a range (`$B$2:$C$4`) and a second click replaces the reference just written, as in Excel ("Selecting cells on the worksheet inserts absolute cell references"). The selection the rule applies to doesn't move, and the box keeps the keyboard. After an operand (`=A1>5`) the click does nothing (#2348) | | 2026-10-23 | minor | Frozen panes: Page Up, Ctrl+Up and Shift variants that move from the scrolled rows into the frozen ones bring the scrolled rows back to the top, so the rows in between are seen again (Page Up from far down used to end on row 1 with the view still a page down); Ctrl+Left and Alt+Page Up do the same for frozen columns. Other ways of selecting a frozen cell (click, Find, Go To, Undo, formula pointing) leave the view where it is |