mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-25 03:06:53 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b9f6ff01 |
@@ -667,7 +667,7 @@ fn go_to_target(wb: &gridcraft_model::Workbook, sheet: usize, part: &str, depth:
|
||||
return Some((on, vec![r], false));
|
||||
}
|
||||
let name = match prefix {
|
||||
Some(_) => wb.names.iter().find(|n| n.scope == Some(on) && n.name.eq_ignore_ascii_case(body)),
|
||||
Some(_) => wb.names.iter().find(|n| n.scope == Some(on) && gridcraft_formula::same_name(&n.name, body)),
|
||||
None => wb.name(body, sheet),
|
||||
};
|
||||
if let Some(n) = name {
|
||||
@@ -709,20 +709,21 @@ fn name_areas(wb: &gridcraft_model::Workbook, n: &gridcraft_model::DefinedName,
|
||||
/// and the active sheet's own name hides the workbook name it shares. Hidden names and names that
|
||||
/// do not refer to a range are left out.
|
||||
pub fn go_to_names(wb: &gridcraft_model::Workbook, sheet: usize) -> Vec<String> {
|
||||
let own: std::collections::HashSet<String> = wb.names.iter().filter(|n| n.scope == Some(sheet)).map(|n| n.name.to_ascii_lowercase()).collect();
|
||||
let own: std::collections::HashSet<String> =
|
||||
wb.names.iter().filter(|n| n.scope == Some(sheet)).map(|n| gridcraft_formula::fold_name(&n.name)).collect();
|
||||
let mut out: Vec<String> = wb
|
||||
.names
|
||||
.iter()
|
||||
.filter(|n| !n.hidden && name_areas(wb, n, sheet, 0).is_some())
|
||||
.filter_map(|n| match n.scope {
|
||||
None if own.contains(&n.name.to_ascii_lowercase()) => None,
|
||||
None if own.contains(&gridcraft_formula::fold_name(&n.name)) => None,
|
||||
None => Some(n.name.clone()),
|
||||
Some(s) if s == sheet => Some(n.name.clone()),
|
||||
Some(s) => Some(format!("{}!{}", gridcraft_core::quote_sheet(&wb.sheet(s)?.name), n.name)),
|
||||
})
|
||||
.collect();
|
||||
out.sort_by_key(|n| n.to_lowercase());
|
||||
out.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
|
||||
out.sort_by_cached_key(|n| gridcraft_formula::fold_name(n));
|
||||
out.dedup_by(|a, b| gridcraft_formula::same_name(a, b));
|
||||
out
|
||||
}
|
||||
|
||||
@@ -1971,6 +1972,19 @@ mod tests {
|
||||
assert_eq!(s.doc().unwrap().wb.active_sheet, 0);
|
||||
}
|
||||
|
||||
/// A name matches in any case and any script, as a formula matches it.
|
||||
#[test]
|
||||
fn go_to_matches_a_sheet_name_in_any_case() {
|
||||
let mut s = go_to_workbook();
|
||||
s.execute("formulas.defineName", json!({"name": "Größe", "refersTo": "=Sheet2!$D$4", "scope": "Sheet2"})).unwrap();
|
||||
s.execute("formulas.defineName", json!({"name": "GRÖSSE", "refersTo": "=Sheet1!$F$6", "scope": "Workbook"})).unwrap();
|
||||
let out = s.execute("edit.goTo", json!({"reference": "Sheet2!GRÖSSE"})).unwrap();
|
||||
assert_eq!(out["selection"], "D4", "{out}");
|
||||
// Seen from Sheet2, its own name hides the workbook name it shares.
|
||||
let list = s.execute("edit.goTo", json!({"list": true})).unwrap();
|
||||
assert_eq!(list["names"], json!(["Areas", "Größe", "Local", "Price", "Sheet1!Own", "type"]), "{list}");
|
||||
}
|
||||
|
||||
/// "You can enter multiple named ranges or cell references in the Reference box. Separating
|
||||
/// each with a comma ... Excel will highlight all the ranges."
|
||||
#[test]
|
||||
|
||||
@@ -2587,16 +2587,16 @@ fn paste_name(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm: &m
|
||||
}
|
||||
d.list_index = d.list_index.min(last);
|
||||
ui.label(app.ui.language.tr("Paste name"));
|
||||
egui::ScrollArea::vertical().max_height(220.0).show(ui, |ui| {
|
||||
ui.set_min_width(ui.available_width());
|
||||
if names.is_empty() {
|
||||
ui.label(egui::RichText::new(app.ui.language.tr("No names defined")).italics());
|
||||
}
|
||||
for (i, n) in names.iter().enumerate() {
|
||||
if names.is_empty() {
|
||||
ui.label(egui::RichText::new(app.ui.language.tr("No names defined")).italics());
|
||||
}
|
||||
// A workbook can hold thousands of names: only the rows in view are laid out.
|
||||
let row_height = ui.text_style_height(&egui::TextStyle::Button) + 2.0 * ui.spacing().button_padding.y;
|
||||
let step = row_height + ui.spacing().item_spacing.y;
|
||||
egui::ScrollArea::vertical().max_height(220.0).auto_shrink([false, true]).show_rows(ui, row_height, names.len(), |ui, rows| {
|
||||
let top = ui.cursor().top() - rows.start as f32 * step;
|
||||
for (i, n) in names.iter().enumerate().skip(rows.start).take(rows.len()) {
|
||||
let r = ui.selectable_label(i == d.list_index, n);
|
||||
if (down || up) && i == d.list_index && !ui.clip_rect().contains_rect(r.rect) {
|
||||
r.scroll_to_me(Some(if down { egui::Align::Max } else { egui::Align::Min }));
|
||||
}
|
||||
if r.clicked() {
|
||||
d.list_index = i;
|
||||
}
|
||||
@@ -2604,6 +2604,13 @@ fn paste_name(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm: &m
|
||||
*confirm = true;
|
||||
}
|
||||
}
|
||||
// The arrow keys can move the selection onto a row that isn't laid out.
|
||||
if down || up {
|
||||
let rect = egui::Rect::from_min_size(egui::pos2(ui.min_rect().left(), top + d.list_index as f32 * step), egui::vec2(1.0, row_height));
|
||||
if !ui.clip_rect().contains_rect(rect) {
|
||||
ui.scroll_to_rect(rect, Some(if down { egui::Align::Max } else { egui::Align::Min }));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
// Paste List is disabled while a cell is being edited.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Function parity: worksheet functions and formulas
|
||||
|
||||
> **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-23 · **Change:** minor (number text with accounting parentheses coerces like Excel 365; INDEX with one index on a 2-D reference is #REF! as in Excel; earlier: SUBTOTAL and AGGREGATE cap the cells they visit, #2175) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-25 · **Change:** minor (the LAMBDA row lists the recursion limit and its `#NUM!`, and the error a defined name holding an error gives when called; earlier: number text with accounting parentheses coerces like Excel 365; INDEX with one index on a 2-D reference is #REF! as in Excel; earlier: SUBTOTAL and AGGREGATE cap the cells they visit, #2175) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
|
||||
Excel's worksheet functions, category by category, against GridCraft's function library. Part of
|
||||
[target-app-parity.md](target-app-parity.md); gaps feed [gaps.md](gaps.md).
|
||||
@@ -87,7 +87,7 @@ This improves series preparation; it does not change the model-fitting or season
|
||||
| Implicit intersection `@`, legacy CSE array formulas | done, the entered range filled (PR #156) |
|
||||
| Array lifting: an array in a scalar argument evaluates the function per element | done. The Analysis ToolPak functions (EOMONTH, EDATE, WORKDAY(.INTL), NETWORKDAYS(.INTL), YEARFRAC, WEEKNUM, the COUP*, PRICE*, YIELD*, ODD*, TBILL*, ACCRINT*, AMOR*, DISC, DURATION, CUMIPMT/CUMPRINC, EFFECT, NOMINAL, DOLLARDE/FR, XNPV/XIRR rate and guess, FVSCHEDULE principal, ISEVEN/ISODD, QUOTIENT, MROUND, FACTDOUBLE, SQRTPI, RANDBETWEEN, SERIESSUM, the base conversions, CONVERT, DELTA, GESTEP, ERF/ERFC, BESSEL*, COMPLEX and IM* except IMSUM/IMPRODUCT) give `#VALUE!` for a reference to more than one cell in a value argument, as Excel 365 does in both dynamic-array and legacy formulas; array constants and computed arrays (`A1:A3+0`) still lift, and range arguments (holidays, schedule, cash flows) and the .INTL weekend accept ranges. Difference: BYROW/BYCOL pass their row or column to the LAMBDA as values, so `BYROW(A1:B3,LAMBDA(r,EOMONTH(r,0)))` lifts where Excel gives `#VALUE!` |
|
||||
| Trim references `A1:.E10` (trailing), `A1.:E10` (leading), `A1.:.E10` (both), also on whole rows/columns (`A:.A`) | done; evaluated as `TRIMRANGE(range, code, code)`, adjusted on insert/delete; files store `_xlfn.TRIMRANGE(range,code,code)` (Excel's own file spelling unconfirmed in public docs), `_xlfn._TRO_ALL/_TRO_LEADING/_TRO_TRAILING(...)` read too |
|
||||
| LET, LAMBDA, named LAMBDAs, ISOMITTED, MAP/REDUCE/SCAN/BYROW/BYCOL/MAKEARRAY | done |
|
||||
| LET, LAMBDA, named LAMBDAs, ISOMITTED, MAP/REDUCE/SCAN/BYROW/BYCOL/MAKEARRAY | done. A LAMBDA that calls itself more than 140 times, or a formula nested past the depth limit, gives `#NUM!` (the target app allows deeper recursion, #1581). Calling a defined name that holds an error value gives that error, not `#NAME?` |
|
||||
| Iterative calculation for circular references | done: with Enable Iterative Calculation on (`formulas.calculationOptions` `{iterative, maxIterations, maxChange}`, saved as `<calcPr iterate iterateCount iterateDelta>`), loops are evaluated again from their previous values (blank = 0) until no cell on a loop changes by Maximum Change or Maximum Iterations is reached; off, cycles give `#CIRC!`. Differences: no Options dialog yet (command only); GridCraft recalculates on open, so a loop that never settles (`=A1+1`) advances again each time the file is opened |
|
||||
| 15-significant-digit comparison semantics | done (#57): `=0.1+0.2=0.3` is TRUE, and MATCH and COUNTIF compare the same way. Arithmetic keeps full precision, as Excel does — `=(0.1+0.2)-0.3` is 5.55E-17 |
|
||||
| External workbook references (`[Book.xlsx]Sheet1!A1`) | parsed; links dropped on open, no update |
|
||||
@@ -123,6 +123,7 @@ workbooks; results checked in as data, not Excel files) and fixing what it finds
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-25 | minor | LAMBDA row: a LAMBDA that calls itself more than 140 times, and a formula nested past the depth limit, give `#NUM!` (#1809; nesting gave `#CALC!` before). `=Foo(1)` where the defined name Foo holds `#N/A` gives `#N/A`, not `#NAME?` |
|
||||
| 2026-10-23 | minor | Number text coercion (VALUE, arithmetic, typed entry): a dollar sign before accounting parentheses (`$(1,234)`, `$ (5)`) and a percent sign after them (`(5)%`) read as negatives; `(-5)`, `((5))`, `($-5)`, `$5%`, `$(5)%` and dates or times in parentheses are `#VALUE!`, as observed in Excel 365 |
|
||||
| 2026-10-23 | minor | INDEX with only a row index on a reference of several rows and columns (`INDEX(A1:C3,2)`, `INDEX(A1:C3,0)`) is `#REF!`, as observed in Excel, so it also fails as a range endpoint and inside other functions; an empty column argument (`INDEX(A1:C3,2,)`) still gives the row. Checked that a column array with one index stays a 1×1 array (`TYPE` 64), which is what Excel returns |
|
||||
| 2026-10-21 | minor | SUBTOTAL and AGGREGATE charge every argument against the evaluator's 16-million-cell limit before scanning; a value in XFD1048576 no longer makes `SUBTOTAL(9,A:XFD)` read the whole sheet (#2175) |
|
||||
|
||||
+3
-3
@@ -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; Ctrl+Shift+U expands and collapses the formula bar; Fill Days, Fill Weekdays, Fill Months and Fill Years in the fill-handle menus for a date; Show Details in the cell menu of a PivotTable value cell; dragging the handle between the sheet tabs and the horizontal scrollbar resizes the tabs; Outline Settings: summary rows above and summary columns left of their detail; 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-25 · **Change:** minor (Paste Name lays out only the names in view; Go To matches names in any case and script; one Page Layout view row, with the header and footer side margin marked unverified; earlier: 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; Fill Days, Fill Weekdays, Fill Months and Fill Years in the fill-handle menus for a date; Show Details in the cell menu of a PivotTable value cell; dragging the handle between the sheet tabs and the horizontal scrollbar resizes the tabs; Outline Settings: summary rows above and summary columns left of their detail; 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
|
||||
@@ -33,8 +33,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes
|
||||
| Dark mode, system appearance | yes | yes | done (10-09); follows the system on first run, title-bar switch and File › Display theme (#194, #239); icons, reference colours, primary buttons and the comments pane follow the theme, and a unit test holds every token pair to WCAG 2 contrast in both themes |
|
||||
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | yes (#174, 10-11): the File tab opens a full-window view (Esc or the back arrow return) with Home, New (blank + the three samples), Open (recent + Browse; the recent list tells when each workbook was last changed, 10-12), Info (location, properties, Protect Workbook), Save, Save As, Print (page preview, active sheet or workbook, fit to page, Page Setup), Export (PDF, CSV, web page), Close and Options (`file.options`: theme, language, formula bar, AutoSave, calculation) | no Account, Share or template gallery beyond the samples; Options applies at once (no OK/Cancel dialog) and holds only the settings GridCraft has |
|
||||
| Dark mode, system appearance | yes | yes | done (10-09) |
|
||||
| Page Layout view (paper view with rulers and header editing) | yes | yes (#329, 10-11): per-sheet view saved in XLSX, pages with margins and gaps on the print pagination, "Click to add data" pages, rulers (inches or centimetres), headers and footers drawn and edited in place with a Header & Footer tab, whose Different First Page and Different Odd & Even Pages give the first page and the even pages their own header and footer (#344) | margins drag on the rulers (#792, with header and footer margins); header/footer pictures and the Scale with Document / Align with Page Margins check boxes missing (XLSX keeps both settings) (1–3 h) |
|
||||
| Page Layout view (paper view with rulers and header editing) | yes | yes (#329, 10-11): per-sheet view saved in XLSX, pages with margins and gaps on the print pagination, "Click to add data" pages, rulers (inches or centimetres), headers and footers drawn and edited in place with a Header & Footer tab, whose Scale with Document and Align with Page Margins set whether header and footer text follows the print scale and the side margins, on paper and in the view | margins drag on the rulers (#792, with header and footer margins); header/footer pictures, Different First Page / Odd & Even missing (1–3 h) |
|
||||
| Page Layout view (paper view with rulers and header editing) | yes | yes (#329, 10-11): per-sheet view saved in XLSX, pages with margins and gaps on the print pagination, "Click to add data" pages, rulers (inches or centimetres), headers and footers drawn and edited in place with a Header & Footer tab, whose Different First Page and Different Odd & Even Pages give the first page and the even pages their own header and footer (#344), and whose Scale with Document and Align with Page Margins set whether header and footer text follows the print scale and the side margins, on paper and in the view | margins drag on the rulers (#792, with header and footer margins); header/footer pictures missing (1–3 h). Unverified: with Align with Page Margins off, header and footer text sits 0.7 in from the paper edge, a value not yet measured in the target app |
|
||||
| Multiple windows per workbook | yes | no (#162) | see [hardware-parity.md](hardware-parity.md) |
|
||||
|
||||
## Grid interaction
|
||||
@@ -187,6 +186,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-25 | minor | Paste Name lays out only the names in view, so a workbook with thousands of names opens it at once; the arrow keys still scroll the chosen name into view. Go To matches a `Sheet2!Name` reference and hides a workbook name behind the active sheet's own name in any case and script (`Größe` = `GRÖSSE`), as formulas do. The two Page Layout view rows are one; the 0.7 in side margin used when Align with Page Margins is off is marked unverified |
|
||||
| 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) |
|
||||
|
||||
Reference in New Issue
Block a user