Commit Graph
93 Commits
Author SHA1 Message Date
aiongg 4c81d658ec Integration: track t05
# Conflicts:
#	crates/ui-egui/tests/keyboard_shortcuts.rs
#	docs/ui-parity.md
2026-10-22 03:01:39 +00:00
aiongg 512b2aa9fd Integration: track t04
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/edit.rs
#	crates/engine/src/cmd/formulas.rs
#	crates/engine/src/tests.rs
#	crates/formula/src/lib.rs
#	crates/formula/src/parser.rs
#	crates/formula/src/tests.rs
#	crates/ui-egui/src/control.rs
#	crates/ui-egui/src/formula_bar.rs
#	crates/ui-egui/src/grid.rs
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/ribbon.rs
#	crates/ui-egui/tests/selection.rs
#	docs/function-parity.md
#	docs/ui-parity.md
2026-10-22 03:01:31 +00:00
aiongg 54d0f7cfc0 Integration: track t02
# Conflicts:
#	ROADMAP.md
#	crates/core/src/lib.rs
#	crates/ui-egui/src/editor.rs
#	crates/ui-egui/src/grid.rs
#	crates/ui-egui/src/tabs.rs
#	docs/chart-parity.md
#	docs/localization-parity.md
#	docs/ui-parity.md
2026-10-22 02:59:55 +00:00
aiongg 8ec29a8b3b Merge PR #1652: Fill handle: right-drag opens the fill menu (Copy Cells, Fill Series, Fill Formatting Only, Fill Without Formatting, Series…)
crates/ui-egui/src/grid.rs: kept both GridState fields (context_menu_frame, fill_menu); previous_popups tuple has four elements inside the touch_pan branch; release match has Drag::Rows|Cols, then the two Drag::Fill arms
docs/ui-parity.md: kept HEAD header line, both change rows
2026-10-22 02:36:24 +00:00
aiongg cba89cfda8 Merge PR #1066: Engine: formulas.evaluate refuses what cell entry refuses, saying why
ROADMAP.md: both progress-log rows kept.
2026-10-22 02:36:20 +00:00
aiongg 8590dca0db Merge PR #2207: clipboard: bound HTML colspan/rowspan expansion (#2171) 2026-10-22 02:36:12 +00:00
aiongg d0394a0f34 Merge PR #2005: AutoFill refuses a target that doesn't hold the source in a corner (whole rows into whole columns aborted the app)
crates/engine/src/fill.rs: kept both tests (#1360 and #2005).
2026-10-22 02:36:12 +00:00
aiongg c03d4a881b Merge PR #1570: XLSX: keep dynamic-array cell metadata (cm) on save
ROADMAP.md: kept both progress-log rows
2026-10-22 02:36:04 +00:00
aiongg 6e66756ce8 Merge PR #1976: Enter bare numbers in Percentage-formatted cells as percents 2026-10-22 02:35:44 +00:00
aiongg 0bf7d40f3b Integration: #1534 remove the What::Hyperlinks paste-special target
The hyperlink copy stays inside What::All, which the cut path uses; the existing
test a_cut_moves_the_sources_notes_hyperlinks_and_merges covers it.
2026-10-22 02:35:17 +00:00
aiongg 69f6ea363d Merge PR #1529: fix #1458: one reference-app sheet-name quoting rule for formulas, pivot sources and ADDRESS 2026-10-22 02:35:16 +00:00
aiongg 2c10a8da46 Merge PR #759: Names: a name over several ranges reads its top-level commas as unions
crates/formula/src/lib.rs: union pub use list (MAX_FORMULA_LEN and parse_name).
crates/engine/src/cmd/formulas.rs: keep both new tests.
2026-10-22 02:35:11 +00:00
aiongg 51401a382c Merge PR #1534: engine: a cut takes the source's notes, hyperlinks and merged areas with it 2026-10-22 02:35:01 +00:00
aiongg 0f27cef4d6 Merge PR #1364: engine: keep case-insensitive replace from dropping text when folding shifts byte boundaries 2026-10-22 02:34:59 +00:00
aiongg 675d96ef17 Merge PR #1373: Protection: Protect Sheet entries unprotect, selectLockedCells enforced (#1346) 2026-10-22 02:34:56 +00:00
aiongg 78225db6f8 Merge PR #1108: Refuse a cut-paste whose destination does not fit instead of dropping cells (#997) 2026-10-22 02:34:52 +00:00
aiongg 2c73c2b18f Merge PR #1214: Grid: an armed Format Painter pastes onto the next cells clicked or dragged 2026-10-22 02:34:39 +00:00
Higgs32584andClaude Opus 5.5 b817c04a9b clipboard: bound HTML colspan/rowspan expansion (#2171)
Clipboard HTML is read only as far as the pasted text's shape (clamped
to the sheet) and at most 1,000,000 cells; colspan/rowspan are clamped
to the sheet extent before expanding, and rowspan now keeps later rows
in their columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:06:51 -04:00
kezmdorandClaude Opus 5.5 cfb0e186ed Formula entry: an incomplete formula's error describes what was typed (fixes #2030)
`=1+` reported "unexpected `)`", although no `)` was typed: entry closes
missing parentheses by appending up to eight `)`, and when every retry
failed it reported the last retry's error. It now reports the error of
the formula as typed ("unexpected end of formula").

`gridcraft_formula::parse_closing_parens` holds the convenience and takes
the parse function, so cell entry and the UI's Spanish formula path share
it instead of each having its own loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:44:09 +02:00
Eduardo GarciaandClaude Sonnet 5.5 0b4cc5618f fix #1458: one Excel sheet-name quoting rule for formulas, pivot sources and ADDRESS
Sheet names starting with '.' or reading as R1C1 (R, C, RC, R1C1...) were saved
unquoted, so Excel could not open the file; PivotTable sources and ADDRESS left
cell-like names (A1) bare. The quoting rule, unquoting and sheet splitting now
live once in gridcraft_core::addr and replace the copies in the formula printer,
pivot, ADDRESS, chart, xlsx, Solver, Go To and the Name Box (which now reads ''
as ').

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 17:36:40 -03:00
NetterSilverandClaude Haiku 5.5 3a2232cfef Enter bare numbers in Percentage-formatted cells as percents
Fixes #1967

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 18:31:57 +02:00
Brandon Thomas 937cfd1e91 Merge pull request #1903 from justinjohn0306:fix/1855-backspace-undo 2026-10-19 12:21:29 -04:00
Vedran DobosandClaude Opus 5.5 6ac8ad9216 fix(edit): Ctrl+Enter gives each cell the number format typing gives it
range.fill parsed the input once at the active cell, then put back the old
style of every target that already held a cell, so 5%, $5, 3/4/2026 or
=DATE(...) lost their automatic format there (0.05, 5, 46085), and empty
targets took the active cell's style. Each target now gets what
input_to_cell gives for it (parsed once per distinct style), as typing does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:18:34 +02:00
justinjohn0306andClaude Opus 5.5 dcd4d1d13f #1855: redo selects the redone cell
Undo stored the selection at the moment of undoing (wherever the user had
clicked) in the redo entry, so redo jumped back there instead of to the
cells it changed. The redo entry now keeps the undone step's selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:16:13 +05:30
Harrison BoothandClaude Sonnet 5.5 70fc5e1c78 Merge main into fix/workbook-metadata-part
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 13:13:22 -05:00
Marco Arnulfo 295f7dde33 Fill handle: right-drag opens the fill menu (Copy Cells, Fill Series, Fill Formatting Only, Fill Without Formatting, Series…) 2026-10-16 14:20:48 +02:00
Harrison O'Connor-Hoover 79e85c1b80 fix(edit): recalculate every cell changed by large clears (#1032) (#1128) 2026-10-15 13:38:13 -06:00
Marco Arnulfo 7a352cee09 Grid: an armed Format Painter paints whole rows and columns from their headers (refs #1211) 2026-10-15 21:22:40 +02: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
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
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
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 686c13c8b7 engine: keep case-insensitive replace from dropping text when folding shifts byte boundaries 2026-10-14 23:22:05 +03:00
MarekSpodBiedry 2c8ff394cc Refuse a cut-paste whose destination does not fit instead of dropping cells (#997) 2026-10-13 18:04:55 +02:00
quantmind-br 482c3e9481 Engine: formulas.evaluate refuses what cell entry refuses, saying why
`formulas.evaluate` (behind `gridcraft-cli eval` and MCP's `evaluate_formula`) turned any
formula that didn't parse into #NAME?, so `=SUM(1;2)` looked like an unknown function. It now
parses the way `cell.set` does (missing closing parentheses are added) and returns the same
errors: "There's a problem with this formula: expected `,` or `)`, found Semicolon", or the
wrong-argument-count message. An unknown function still evaluates to #NAME?, as in Excel.
2026-10-13 09:38:41 -03:00
Brandon Thomas 2d0a3f8a47 Merge main into #780
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/insert.rs
#	crates/engine/src/tests.rs
2026-10-13 01:02:28 -04:00
Marco Arnulfo d231823e1c Merge remote-tracking branch 'origin/main' into fix/name-union-definition 2026-10-13 06:22:09 +02:00
Brandon Thomas d6bb4bdf7a Merge pull request #763 from msaulc:fix/ctrl-shift-arrow-scroll 2026-10-12 23:49:25 -04:00
Brandon Thomas a435ef6ada Merge pull request #720 from dantebertuzzi:fix/cell-set-numeric-input 2026-10-12 23:49:02 -04:00
vangel76andClaude Sonnet 5.5 2797d468c5 Security: cell hyperlinks open only http, https and mailto, after a confirmation (#777)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 20:17:30 +02:00
Marco Arnulfo cf587ffe3b Names: a name over several ranges reads its top-level commas as unions (fixes #728) 2026-10-12 19:16:49 +02:00
msaulcandClaude Opus 5.5 77a91a714a Grid: extending the selection from the keyboard scrolls its moving end into view
Ctrl+Shift+Arrow (and Shift+Arrow, Shift+PageUp/PageDown, Alt+Shift+PageUp/PageDown,
Ctrl+Shift+Home/End, Shift+Home) extend the selection by moving its far corner while the
active cell stays on the anchor. After the move the grid set `ensure_visible`, but the
scroll-into-view step in `grid::show` always targeted `selection.active`, which is the
anchor and is already on screen. So the view never followed the extension past the last
visible row or column.

The keyboard extension paths now also set `GridState::reveal_moving_end`, and the
ensure-visible step then scrolls to `Selection::moving_end()`, the corner opposite the
anchor. `selection.move` now uses that same helper (it had the logic inline) to find where
an extension starts. Other ensure-visible callers (Go To, Name Box, plain moves) still
reveal the active cell.

Regression test: crates/ui-egui/tests/selection_scroll.rs drives Ctrl+Shift+Up/Down/
Left/Right, Shift+Down/Right and Ctrl+Shift+Home/End through real egui key events and
checks that the moving end ends up on screen while the anchor and active cell stay put.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:07:43 -06:00
dantebertuzziandClaude Opus 5.5 857f538c08 cell.set / set_cell: accept a number for input (#697)
`input` is documented as "text, number or =formula", but only a string
was read; a JSON number was dropped and the command failed (or, on 0.5.0,
silently cleared the cell). The undocumented `value` alias already went
through `json_to_input`. Both keys now share that converter, so numbers
and booleans are entered as typed. The MCP `set_cell` schema types
`input` as string, number or boolean to match its description.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:15:06 -03:00
MarekSpodBiedry 125f460cef Paste Special: copy the validation rule with the Validation and All options (#562) 2026-10-12 08:36:13 +02:00
Brandon Thomas 0a54876e2c Merge pull request #425 from neilcawse/test/command-sequence-fuzz
Engine: command-sequence fuzz harness (undo/redo, XLSX round-trip, full-recalc invariants) and the bugs it found
2026-10-11 18:06:03 -04:00
Shirish Veerabattini 3936f3b79c engine: correct which functions the entry check skips
IF, IFERROR, CHOOSE, SWITCH and INDEX are in the registry and are checked; the comment listed IF as skipped.

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 16:10:04 -05:00
neilcawseandClaude Opus 5.5 828efab385 Engine: command-sequence fuzz harness (undo/redo, XLSX round-trip, full-recalc invariants) and the bugs it found
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:39:31 -04:00
Shirish Veerabattini 993c108660 functions: share the argument-count rule between call and entry
FnSpec::accepts holds the arity check (including Excel's 255-argument
limit) that functions::call and the entry check both use, so entry also
refuses 256 arguments to a variadic function. The evaluator's own check
in calc is unchanged.

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 15:37:09 -05:00
Shirish Veerabattini 9b59966cc9 Merge upstream main into fix/formula-arg-count-363
Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 15:20:56 -05:00
Shirish Veerabattini 56f784473d engine: refuse formulas with the wrong argument count on entry
A formula calling a built-in function with too few or too many
arguments (=DATEDIF(a,b), =SUM(), =DATE(2024,1)) was stored and showed
#VALUE! with no hint about what was wrong. Entry now refuses it with
the function's signature, e.g. "DATEDIF has too few arguments. Use
DATEDIF(start_date, end_date, unit)", and leaves the cell unchanged.

Only registered functions are checked: special forms the evaluator
handles itself (IF, LET, LAMBDA...), LAMBDA names and unknown functions
are skipped. Paste and Find & Replace keep turning input that fails
entry into text. DATEDIF's description now lists its units.

Fixes #363

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
2026-10-11 13:29:14 -05:00