Commit Graph
1496 Commits
Author SHA1 Message Date
midasmatterhorn f8cbfb7cb6 engine: sort and remove duplicates move the rows' notes and hyperlinks
do_sort moved cells, formulas, cell pictures, row heights and floating
object anchors through the sort permutation, and remove_duplicates moved
cells and pictures through the compaction, but neither touched
Sheet::comments or Sheet::hyperlinks (keyed by CellRef): a note that
described one record stayed behind and annotated whatever landed on that
row — 'late payment' on a paid invoice — and a note on a row removed by
Remove Duplicates annotated blank cells. Sheet::comments/hyperlinks are
exactly the range-scoped side data that row and column inserts already
remap and edit.clear already clears, so the omission looks unintended.

Both maps now travel through the same permutation the cells use (the
sort collects before re-inserting, so a swap cannot resurrect an entry
from a destination that is also a source; Remove Duplicates compacts the
kept rows and drops the rest).

Regression test: sort_and_remove_duplicates_move_the_rows_notes_and_links
— on main the note and link stay on their old cells; with the fix they
travel with their record.
2026-10-15 17:40:18 +03:00
midasmatterhorn e6c0f92d59 engine: a cut takes the source's notes, hyperlinks and merged areas with it
A cut removed the source's cells and pictures and then re-created the
block's merged areas at the destination and copied its notes there — but
removed nothing from the source, so one merged block became two (an
empty one left behind) and a note ended up on both the old and the new
cell. Excel moves both and leaves the source clean.

The cut now clears the source range's comments, hyperlinks and merges
the same way edit.clear does. A second gap in the same path: paste never
transferred hyperlinks at all, so clearing the source would have lost
them — paste now carries hyperlinks (in What::All and a new
What::Hyperlinks, so edit.pasteSpecial {"what":"hyperlinks"} works
like "comments").

Regression test: a_cut_moves_the_sources_notes_hyperlinks_and_merges —
on main the old merge stays behind and the note is on both cells; with
the fix the merge, note and link all move and the source is clean.
2026-10-15 17:30:35 +03:00
midasmatterhorn 7deb382f3d xlsx: keep a table's filter criteria when the sheet AutoFilter coincides with it
The engine's live filter state is always sheet.autofilter — apply_filter,
filter_values, clear_filter, reapply and the slicer command all read and
write it, and a table's header dropdown installs one over the table's own
range — while Table::filter is only ever filled by the reader. Writing
suppressed the sheet <autoFilter> whenever any table merely intersected
it and wrote the table part from Table::filter, which was empty: a
filtered table saved with its rows still hidden and no criteria anywhere,
so on reopen there was no filter to clear and Excel showed an inactive
filter over mysteriously hidden rows. A slicer's selection was lost the
same way.

Fold the coinciding sheet filter's criteria into the table part (colId
offsets are relative to the range's first column, and both ranges start
at the same column, so they carry over verbatim); a filter that only
partially covers a table still has nowhere valid to go and keeps being
suppressed.

Regression test: a_table_filter_criteria_survive_a_round_trip — on main
the table part carries no criteria after a save; with the fold they round
trip, the hidden row stays, and the sheet keeps no duplicate autoFilter.
2026-10-15 17:22:31 +03:00
midasmatterhorn 18fecf8e8c engine: pageLayout.printTitles only touches the axes it is given
print_titles read both rows and cols as Options and assigned both
unconditionally, so {"rows":"2:2"} set title_cols = None and
{"cols":"A:A"} set title_rows = None: the repeated rows or columns
disappeared from every printed page with no message, and there was no
way to set or clear just one axis. The sibling pageLayout.pageSetup
already distinguishes "key absent" from "key present but empty"; read
both axes the same way — absent leaves the axis alone, empty/null clears
it, anything else that is not a range is an error rather than a silent
wipe.

Regression test: print_titles_keeps_the_axis_it_is_not_given — on main
the rows-only call clears the columns; with the fix each axis is touched
only when named, an explicit empty value still clears, and a bad range
errors.
2026-10-15 17:18:19 +03:00
midasmatterhorn 3f9b73b927 functions: NETWORKDAYS.INTL accepts the all-weekend string "1111111"
weekend_mask rejected the all-weekend string outright for both
NETWORKDAYS.INTL and WORKDAY.INTL, but Microsoft documents them as
differing: NETWORKDAYS.INTL "Using 1111111 will always return 0",
WORKDAY.INTL "1111111 is an invalid string". The crate already
implements that split — workday_core's per_week == 0 guard — which was
unreachable dead code while the extra clause stood. Drop the clause:
NETWORKDAYS.INTL counts zero workdays as documented and WORKDAY.INTL
errors through the guard it already has.

The existing test asserted the old behaviour and is updated.
Regression: working_days — on main the NETWORKDAYS.INTL assertion fails
(#VALUE! where Excel returns 0); with the clause dropped it passes and
WORKDAY.INTL is asserted to error.
2026-10-15 17:12:41 +03:00
NetterSilverandClaude Haiku 5.5 67be35564c Keep leading empty columns and rows when saving CSV/text
Found while testing crates/xlsx/src/csv.rs:204 (write_csv_limited, starts at sheet.used_range().start instead of A1)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 16:01:47 +02:00
Eduardo GarciaandClaude Sonnet 5.5 ea01ad999b fix #1443: Ctrl+Q no longer closes the window outside macOS
egui's default quit shortcut closed the window without asking to save.
Clear it off macOS (Cmd+Q stays the app's Quit there) and add a regression test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 10:49:49 -03:00
quantmind-br 47268a42af UI: function help follows the interface language
The argument hint, autocomplete, Insert Function and the Formulas-tab menus
read per-language tables of function descriptions and argument names
(i18n/<lang>_functions.rs), keyed by the catalog's English text and
binary-searched. The hint is assembled from the formula syntax's function
name and separator plus the language's argument names, all of them or none,
so it never mixes two languages. Insert Function searches the translated
descriptions and shows readable category names. SPANISH_HELP moves to
es_functions.rs with its 10 functions.
2026-10-15 10:49:13 -03:00
justinjohn0306 b930d60bc3 #1505: keep scalar INDEX MATCH formulas scalar 2026-10-15 18:32:00 +05:30
NetterSilverandClaude Haiku 5.5 e1902111d8 Read securities function dates in the workbook's date system
Fixes #1457

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:29:44 +00:00
adrdsouza 7f9f73d6ba fix(numfmt): share legacy colors across grid and PDF rendering 2026-10-15 16:10:07 +05:30
derechopiano 9097170b08 XLSX: preserve carriage returns in saved text 2026-10-15 10:33:57 +01:00
Harrison BoothandClaude Opus 5.5 4567aed5fb fix(engine): AutoFill refuses targets that don't hold the source in a corner
edit.autoFill capped source and target at 5M cells separately, but fill()
writes one cell per source lane per step, and it never checked the target
held the source. Whole rows into whole columns (`2:3` into `A:A`) filled
every one of 16,384 columns down a million rows: ~17 billion writes, then
a 24-51 GB allocation abort that catch_unwind can't stop.

Excel (observed via COM on a synthetic workbook) refuses AutoFill when the
target doesn't contain the source or holds it away from every corner, and
accepts any corner-sharing target. edit.autoFill now applies that rule
(fill::holds_source_in_corner), and fill() bounds lanes x steps by
MAX_FILL_CELLS as a backstop. The 5M literals in auto_fill and fill_dir
use the shared constant.

Regression test: fill::tests::autofill_refuses_targets_that_do_not_extend_the_source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:39:54 -05:00
icsimpiantiandClaude Opus 5.5 e1e02f82f1 i18n(it): Foglio previsione on the ribbon button too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:21:40 +02:00
icsimpiantiandClaude Opus 5.5 a770845ebf i18n(it): align ten labels with Excel's Italian terminology
Larghezza predefinita, Cancella regole dall'intero foglio, Foglio previsione,
Giorno feriale, Singola/Doppia contabilità, Statistiche, Layout rapporto,
and the Space key name in the command search hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:21:15 +02:00
wstock 45644d3f43 Control channel: ui.dialog.confirm sends what OK sends (#1459)
OK runs a dialog's command with build_params(&d), which reshapes the
field values for several dialogs (the rule wrapper of cfQuick, the
delimiters list of Text to Columns, the columns of Remove Duplicates and
Subtotal, Data Validation's unused fields, Zoom presets, Series, Define
Name). ui.dialog.confirm passed the raw field values instead, so a
Highlight Cells rule confirmed over the control channel added nothing
and Text to Columns ignored the ticked delimiter.

Make build_params pub(crate) and use it from ui.dialog.confirm, say so
in the control protocol doc, and add tests driving the Greater Than and
Text to Columns dialogs over the control channel.
2026-10-15 07:06:31 +00:00
wstock 348d955a0a Conditional formatting: text rules never match an error value (#1162)
In Excel the "contains", "begins with" and "ends with" rules are
SEARCH/LEFT/RIGHT formulas, which fail on an error value, so an error
cell is never formatted by them and always matches "does not contain".
GridCraft matched on the error's display text instead, so #N/A
"contained" a and "began with" #.

Skip error values in the three positive rules and match them in
notContainsText, with a regression test over =NA(), =1/0 and text cells.
2026-10-15 06:57:38 +00:00
icsimpiantiandClaude Opus 5.5 2d4a537c6a i18n: add Italian (it) UI translation
Adds Language::It ("it", native name "Italiano") with a new per-language
module crates/ui-egui/src/i18n/it.rs, following the existing pattern
(translate(text) -> Option<&'static str>). The catalog has 971 entries:
every key the most complete catalog (pt) translates, plus the PivotTable
Fields pane strings (from es) and Options|Editing. Labels follow Excel's
Italian terminology (Foglio, Cartella di lavoro, Blocca riquadri, Unisci e
allinea al centro, Somma automatica...). Function names are not touched.

Also: it / it-IT / it_CH tags resolve to Italian (system-language default),
Language::ALL and the coverage/tag/age tests include Italian, and
docs/localization-parity.md lists the new language.

Not compiled locally (no Rust toolchain on the author's machine); relying on CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:52:36 +02:00
euisuhandOpenAI Codex 370b220834 fix(solver): commit solutions and reports atomically
Prepare report data before editing so refused reports cannot leave solution cells or the saved model changed. Commit accepted reports with the optional solution and model, retaining array/protection checks and row autofit after sheet insertion.

Closes #1050

Co-Authored-By: OpenAI Codex <noreply@openai.com>
2026-10-15 15:23:19 +09:00
NetterSilverandClaude Haiku 5.5 cdc8177fae Let GROUPBY and PIVOTBY take a LAMBDA as their function
Fixes #903

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:09:18 +02:00
CouchRiv d59e1cf604 Merge remote-tracking branch 'up/main' into fix/sheet-param-honoured 2026-10-15 01:36:08 -04:00
CouchRiv bc524fa48e Merge remote-tracking branch 'up/main' into fix/conditional-format-style-colors 2026-10-15 01:36:05 -04:00
Camthalion77 4d68300bc2 Add Greek (el) localization 2026-10-15 08:31:40 +03:00
NetterSilverandClaude Haiku 5.5 22157433ce Fix fraction numerators saturating at u64::MAX for large values
Found while testing crates/numfmt/src/render.rs:470

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:48:41 +02:00
NetterSilverandClaude Haiku 5.5 1b4df8acc2 Refuse table names that clash with defined names or R1C1 references
Found while testing crates/engine/src/cmd/insert.rs:477

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:22:02 +02:00
NetterSilverandClaude Haiku 5.5 9b155c48f7 Stop BINOM.INV/CRITBINOM hanging when trials exceed 2^53
Found while testing crates/functions/src/stat.rs:1244

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:43:57 +00:00
Mario f065f9168a Clean up Command Palette code 2026-10-15 05:18:08 +02:00
derechopiano be20ca163d UI: keep PivotTable field-list scrollbar at pane edge 2026-10-15 04:13:02 +01:00
David ArdilaandClaude Opus 5.5 cd94a6d7b4 Recalculation: a spill anchor given a formula that reads its old spill area sees it empty
When a dynamic array's anchor gets a new formula, the formula is evaluated
while the old array is still laid out, so it can read the old spilled
values. The anchor was then never evaluated again: it is left out of its
own spill area's dependents, so as not to read its own array.

- `A1` was `=SEQUENCE(3)` and becomes `=SUM(A2:A3)+1`: it showed 6 (2 + 3
  + 1) with A2:A3 empty; recalculating from scratch gives 1.
- `A1` was `=SEQUENCE(3)` and becomes `=SEQUENCE(1,A2+1)`: it read A2 as 2
  and spilled over A1:C1 instead of showing 1.

The anchor is now evaluated again when its old array is gone or partly
freed. It is still left out when its new array covers the old one, where
it would read its own array.

Regression test in calc
(`an_anchor_given_a_formula_that_reads_its_old_spill_area_sees_it_empty`:
the new formula stops spilling, spills elsewhere, or spills over part of
the old area); it fails without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:05:37 -05:00
David ArdilaandClaude Opus 5.5 ac7595975a Recalculation: formulas reading an array that loses a spill conflict see #SPILL!
When two dynamic arrays of one recalculation want the same empty cells,
both look free while the pass runs, so formulas evaluated in that pass
read both arrays. The one laid out second becomes #SPILL! at write-back,
but nothing evaluated its readers again, and they kept its values.

With B1 `=SEQUENCE(3)` and A2 `=SEQUENCE(1,3)` (both want B2), a full
recalculation (opening a file, Calculate Now) leaves A2 at #SPILL!, yet
`=A2*10` showed 10, `=SUM(A2#)` 6, and `=C2` and `=SUM(C2:C3)` 3, though C2
is empty. Entering the same formulas one at a time gave the right values.

An array blocked at write-back now sends the formulas that read its anchor
or the area it would have spilled into to the next round, where they read
#SPILL! and empty cells.

Regression test in calc
(`formulas_reading_an_array_that_loses_a_spill_conflict_read_spill_errors`);
it fails without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:05:35 -05:00
Mario 4bfd0bc5a1 Improve Command Palette keyboard navigation and layout 2026-10-15 03:23:16 +02:00
Harrison BoothandClaude Opus 5.5 7ee11fe137 XLSX: keep dynamic-array cell metadata (cm) on save
A formula read with `cm` (Excel's XLDAPR dynamic-array record in
xl/metadata.xml) now carries `Formula::dynamic`, and the writer keeps the
`cm` and the metadata part for it even when the formula returns one value
and the export shape analysis would not need array evaluation. Before, such
formulas were saved as legacy formulas, so Excel recalculated some of them
with implicit intersection (SUMPRODUCT(IFERROR(range/range,0)) changed
value), and workbooks whose every dynamic formula was scalar lost
xl/metadata.xml altogether.

The mark survives moves, row and column insertion or deletion, sheet and
name renames and LAMBDA call-case fixes; retyping a formula drops it and
the writer decides from the formula's shape, as before. It is serialized
only when set, so saved documents without it are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:05:03 -05:00
Mario f9802ecc9d Improved font dropdown row sizing and scroll alignment 2026-10-15 01:15:15 +02:00
Harrison BoothandClaude Opus 5.5 ced7243234 calc: bounded references up to 1,048,576 cells keep their full shape (#1010)
A reference over 16,384 cells was cut to the sheet's used range when it was
read into an array. Next to a full-size operand the two no longer lined up:
SUMPRODUCT(D3:AZ400*B3:B400,(A3:A400=1)*(D1:AZ1=2)) gave #VALUE!,
D3:AZ400*B3:B400 filled with #N/A, and ROWS, COUNTBLANK and --(r="") lost
the blanks past the data. Excel keeps the reference's size.

Bounded references now keep their full shape up to one column's worth of
cells (1,048,576). References that run to the edge of the sheet (A:A,
A2:A1048576, several whole rows) and larger blocks still stop at the used
range, so whole-column formulas stay fast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:36:36 -05:00
midasmatterhorn 34a4ed3ead engine: rebuild cell-store bands and counts when importing a JSON workbook 2026-10-15 00:09:25 +03:00
vangel76andClaude Sonnet 5.5 0d18137114 Protection: enforce selectLockedCells/selectUnlockedCells and offer them in Protect Sheet (#1346)
selection.set refuses a cell the protection forbids selecting, selection.move steps on to the
next selectable cell, and review.protectSheet keeps the sheet's current selection rights unless
told otherwise. The dialog gains the two select options and insert/delete columns.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 23:00:50 +02:00
midasmatterhorn d66ed11253 xlsx: keep every property of the default cell format when importing a file 2026-10-14 23:58:57 +03:00
vangel76andClaude Sonnet 5.5 33abe3a9f7 UI: Protect Sheet entries become Unprotect Sheet on a protected sheet (#1346)
Home > Format, HOP and RPS always opened the Protect Sheet dialog, which re-protected
the sheet. open_dialog now turns protectSheet into unprotectSheet for a protected sheet,
and unprotects at once when there is no password.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 22:57:29 +02:00
vangel76andClaude Sonnet 5.5 833fe0da1f Edit: text that would parse as a number or formula keeps its quote prefix in the editor (#1362)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 22:56:36 +02:00
NetterSilverandClaude Haiku 5.5 ac8f6f6ce4 Keep Middle/Bottom vertical alignment for wrapped text taller than its row
Fixes #1347

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 22:56:35 +02:00
midasmatterhorn 2c23b61908 engine: subtotal honours header:false instead of dropping the first data row 2026-10-14 23:51:59 +03:00
DCM4711andClaude Opus 5.5 7b10212b5a Header menus: Format Cells… for the selected rows or columns
As in Excel, right-clicking a row or column heading offers Format
Cells…, between Clear Contents and Row Height… / Column Width….

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:43:51 +02:00
midasmatterhorn 686c13c8b7 engine: keep case-insensitive replace from dropping text when folding shifts byte boundaries 2026-10-14 23:22:05 +03:00
Mario 50f8c0d42b Fix font dropdown resizing and keyboard navigation 2026-10-14 22:16:10 +02:00
midasmatterhorn 938762d796 engine: keep AutoFill text series from panicking or wrapping on i64 overflow 2026-10-14 22:56:07 +03:00
DCM4711andClaude Opus 5.5 d48bcaf043 Context menu: the right-click that opens it doesn't close it
A quick right-click delivers press and release in one frame. The menu
opens at the pointer, and its corner snaps to the nearest physical pixel
(252.46 -> 252.5 on a Retina display), which can leave the pointer just
outside it. The close check then saw that frame's press as a click
outside the menu and closed it right away, so the menu often flashed and
vanished on macOS.

The menu remembers the frame it opened in, and that frame's press no
longer counts as a click elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:55:20 +02:00
DCM4711andClaude Opus 5.5 a0ec1fb785 Selection: whole columns and rows don't widen to merged cells
Clicking column B's header on a sheet with A2:C2 merged selected A:C,
because selection.set grows every area until merged cells are whole.
Excel leaves header selections alone: column B stays B:B and row 3 stays
3:3. Cell ranges still take merged cells whole (B1:B3 -> A1:C3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:24:56 +02:00
Harrison BoothandClaude Opus 5.5 1241fbf260 calc: calculate long dependency chains with bounded stack
A formula that reads one later in row-major order (further down, or on a
later sheet) is calculated on demand, inside the formula that reads it.
Each level costs stack, so real models with chains of about 1,000 such
formulas overflowed the 1 MB main-thread stack on Windows and crashed on
open. Past 2,000 levels the chain gave #CIRC! instead.

On-demand evaluation now goes at most 64 formulas deep. A formula needed
deeper suspends the evaluations in progress: it is calculated from the
top, then the suspended ones again, reading the results kept so far.
Chains of any length calculate, with the same results. A suspended
formula read again is a circular reference, as a formula in progress is,
so loops are found at the same cell as before.

Regression tests build the chains in code: 20,000 formulas each reading
the row below, the same hopping between two sheets, a 5,000-formula loop,
and a loop closing into the middle of a deep chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:08:42 -05:00
Marco Arnulfo 62729e3a99 Keyboard: Alt+= no longer starts an edit with the = it types (fixes #1327) 2026-10-14 19:52:36 +02:00
MarekSpodBiedry ef894c20e5 Reject a list data validation that has no formula1 (#1303) 2026-10-14 19:31:01 +02:00