The 10,000-character header spec was bounded, but each &A, &F or &Z
added the whole sheet name or path again, and a JSON workbook's sheet
name has no length limit. A field that would take a section past
64 KiB is now left out.
edit.fillSeries with dateUnit "weekday" stepped one calendar day at a
time, so a large step cost work proportional to its size for every
cell. It now uses add_weekdays, which moves whole weeks arithmetically.
* functions: match TEXTSPLIT/TEXTBEFORE/TEXTAFTER delimiters through an index, within a work limit
The delimiter matcher compared every delimiter, duplicates included, at
every position of the text. Delimiters are now deduplicated and grouped
by the character a match is anchored on, and a search that would
compare more than 100 million characters returns #VALUE!.
* functions: pass float literals in the delimiter test
The importer kept every cell of a row and only charged their text to
the 64 MiB allowance when the row finished, so one row of long cells
could hold up to 16,384 MiB before being refused. Text is now charged
as each cell finishes; the row charges only its repeats.
* functions: MODE counts through an index, FREQUENCY bins by binary search
MODE/MODE.SNGL/MODE.MULT searched the list of distinct numbers seen so far
for every input, and FREQUENCY scanned the sorted bins for every data value,
so both were quadratic in computed arrays. MODE now keys counts by value
(-0 folded into 0, NaN never merged) and keeps first-occurrence order;
FREQUENCY finds the first bin at or above each value with partition_point
over the order it already sorts. Results are unchanged.
* address review: FREQUENCY keeps NaN data in the overflow bin and NaN bins empty
* calc: list each dependent once when a change spreads
* calc: check the dependency closure against a walk of every cell
Random graphs of single-cell, narrow and wide range reads (some removed or unsettled), random seeds and changed areas: the closure with shared taken records and area lookups equals the one listing every dependent of every changed cell.
* calc: cap the text one & over arrays may build in all
* address review: concatenation over arrays stops at the byte budget without reserving the whole broadcast
Every source record scanned the filter's whole selection, so a large
selection multiplied by many records was slow even when nothing passed.
Each filter's labels are lowercased into a set once per computation.
wrap_lines split an over-long word by re-measuring and copying the
rest of the word for every piece it cut, and only checked its line
limit once the word was done, so one long word in a narrow text box
cost word length x pieces. Each new word also re-measured the whole
line built so far.
Line and piece widths are now running sums of glyph widths (the same
sum text_width makes), so every character is measured once, and the
line limit is checked while a word is being split. The lines produced
are unchanged; a test compares them with the old wrapper.
Importing a workbook whose sheets share a name rescanned every sheet for
every suffix tried, so a file of repeated names opened in cubic time.
SheetNames keeps the assigned names and the next suffix per stem.
Upstream folded cmd/insert/*.rs back into insert.rs, so the merge left
the policy check in a file that is no longer compiled. Restore that file
to upstream and apply the check in the live open_link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- #3223: sniff the first 64 KiB for a web page and skip JSON and files over the text-import cap, so
every CSV/TSV is no longer decoded in full before the size check.
- #3253: SYLK relative offsets use checked arithmetic (no overflow on R[9223372036854775807]); at
most 1024 column-width records are kept; regression test.
- #3254: a single selected merged cell searches the whole sheet, as one selected cell does.
- #3252: the formula bar's height cap is half the room below the ribbon, not half the window, so
the worksheet stays visible in a short window (CodeRabbit Major).
- #3239, #3244, #3252, #3268: es/de/cs/zh/ko/ru/pt entries for the new interface strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses review comment 4456174694: the drag/expand cap was half the
window, so in a 400 pt window a ~195 pt bar left the grid about 1.6 pt
under the status bar and sheet tabs. The cap is now also limited by
the room left under the ribbon, keeping the status bar, sheet tabs,
column headings and two rows below the bar.
Test: ui-egui formula_bar_expand::an_expanded_formula_bar_leaves_room_for_the_worksheet_in_a_short_window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ui.key Enter or ui.click on OK reached egui as ordinary input and
could confirm the openLink prompt. A pass that carries synthetic input
now keeps the prompt open and opens nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses the follow-up in review 5753664551: the protection test had
no range exception, so a regression that refused locked notes and
links inside an Allow Edit Range would have passed. The new test
clears a link in a range without a password, is refused a note in a
password range, then clears both once that range is unlocked.
Test: engine tests::clearing_links_and_notes_keeps_allow_edit_ranges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses review comment 4455670315:
- Clear Hyperlinks and Clear Comments removed links and notes on
locked cells of a protected sheet: protection compares stored cells
only, so their removal went unseen (for stored cells too, and now
for unstored ones since the whole range is cleared). `clear` now
refuses a locked cell's note or link with locked_cell_error, which
keeps Allow Edit Ranges and session-unlocked ranges.
- A removed note or link on a cell with nothing stored wasn't added
to the changed cells, so a recorded macro's footprint missed it.
Tests: engine tests::clearing_links_and_notes_respects_sheet_protection,
tests::clearing_links_and_notes_records_their_cells.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses review comment 4455436875: `clear` clipped each range to the
sheet's used range (stored cells) before removing links and notes, so
a link from a file on an empty cell outside it was left in place, and
nothing was removed on a sheet with no cells. Notes and links are now
cleared over the whole range; cells and merges still only within the
used range.
Test: engine tests::clear_hyperlinks_removes_links_on_cells_with_nothing_stored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
point_move wrote a bare reference, so on another sheet Shift+arrow after a header
drag turned 'Other sheet'!B:D into B:E, which reads as the formula's own sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Page Layout view the "Click to add data" pages are faded after their cells are
painted, which all but hid the band on them. The band goes on again over the fade.
Tests also cover Page Layout view and frozen panes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a drag over C:E, point_cell held C and no anchor was set, so Shift+Right
moved the wrong end and gave D:E. The drag now records the pointer's end as the
moving one and the opposite corner as the anchor, so it gives C:F, and rows work
the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses review comments 4455115540 and 4455115557. The parser stores
`'Sheet'!Name` as `Sheet!Name` with the sheet already unquoted:
- Evaluator::name split it at the first `!`, so a series named
`'a!b'!Title` showed #NAME?.
- chart_name unquoted it again, trimming spaces, so a chart reference
to `' Sales '!Data` was saved as `Sales!Data`.
Both now split at the last `!` and look the sheet up as is.
Tests: chart tests::a_series_named_by_a_name_on_a_sheet_with_a_bang_resolves,
xlsx tests::chart_book_names::chart_names_keep_spaces_around_a_quoted_sheet_name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a wide table the eye loses its place following a row from the active
cell. File > Options > General gets "Highlight the active cell's row", off
by default: a tint across the whole row of the active cell, over the fills
and under the text, in Normal and Page Layout views. It is a preference for
every sheet and workbook, so nothing goes into the file, the workbook does
not become dirty and there is no undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press on a header while a formula waits for a reference inserted C:C, but the
drag that followed did nothing, so dragging from column C to E left C:C. The drag
now follows the pointer, as it does over cells, in either direction and for rows
too, keeping the sheet qualifier, the earlier arguments of a Ctrl press and the
far end of a Shift press.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
table_columns restarted its suffix search at 2 for every duplicate
header, lowercasing and hashing every candidate already in use, so a
table of repeated headers was exported in quadratic time. The next
suffix to try is now remembered per lowercased base.
Addresses review comment 4453674677: after a frame drew both controls,
turning off the horizontal scroll bar or the sheet tabs left
app.grid.hscroll / app.grid.tab_split holding the old rectangles.
sheet_tabs now clears both before any visibility return.
Test: tab_split::hiding_the_controls_clears_their_cached_rectangles
toggles both options on one live harness.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250:
- "Clear parked keytip requests when their window becomes hidden": a
window hidden from another window (View › Hide while it isn't in
front) kept the request it had parked, and showing it again opened
the control. follow_active_window now drops the waiting request of
every parked window that is hidden.
- "Correct the no-badge description in the parity table": only Alt+I,
D, V and T (no ribbon tab) paint no badges.
Test: legacy_keytips::a_window_hidden_from_another_window_drops_its_parked_request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3231, "Reparse consumed separators when a
field is not quoted": parse_tsv read "a<TAB>b"x as one quoted field,
then fell back to its raw text without the separator it had consumed,
so the cells "a, b"x and c pasted back as two. The new copy (which no
longer quotes a cell holding only a quote) makes that text. A field
whose closing quote doesn't end it, or that never closes, is now read
again as unquoted text, so its tabs and line breaks separate cells.
The text read again is bounded by the input's length, so hostile input
stays linear.
Test: a_field_that_is_not_really_quoted_keeps_its_separators.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Clear deferred key-tip requests
when parking a hidden window": a hidden window isn't drawn, so a request
it parked would fire whenever it was shown again (the Symbol gallery
opening long after the keys). Parking a hidden window now drops it; the
rest of its interface state is kept.
Test: legacy_keytips::a_hidden_window_drops_its_waiting_key_tip_request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3229, "Use the same translucency
threshold as committed ink": the preview judged the pen by its width in
points, the committed stroke by its width on screen, so at 50% a 12 pt
highlighter turned opaque once committed and at 200% a 6 pt pen turned
translucent. Both now use chartview::ink_color, which goes by the
stroke's width in points, so the two agree and neither changes with
the zoom.
Test: ink_render::ink_translucency_follows_the_pen_not_the_zoom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Keep deferred requests with their
window": the request waiting for the next frame was shared across
windows, so the root window, drawn first, could take a second window's
Alt+I, S. It is now parked with each window's interface state and moved
into the one-frame request when that window's ui begins.
Test: legacy_keytips::a_waiting_key_tip_request_stays_with_its_window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Expand the ribbon for a same-tab
control request": with the Insert tab chosen but the ribbon collapsed,
Alt+I, S went to the same-tab branch and the Symbol gallery was never
drawn. A collapsed ribbon is now expanded like a tab switch, and the
request waits for the frame that draws the control.
Test: legacy_keytips::alt_i_s_expands_a_collapsed_ribbon_on_the_insert_tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
edit.find and edit.replace take within: selection, which searches only
the selected cells; Find Next then moves the active cell through them
and keeps the range selected, as Excel does. The Find and Replace
dialog uses it whenever more than one cell is selected (Excel searches
a selected range; one selected cell still means the whole sheet).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Open a symbol picker instead of
re-parsing the cell": the legacy key ran insert.symbol with no symbol,
which set the active cell to its own text again (re-reading it: text
could turn into a number, a Python cell lost its code). It now routes to
the ribbon's Symbol control: a control key tip whose control is on
another tab shows that tab first and hands the request to the next
frame, when the control is drawn, and the Symbol button opens its
gallery on its key tip. Nothing is inserted until a symbol is picked.
Test: legacy_keytips::alt_i_s_opens_the_symbol_gallery_and_leaves_the_cell_alone
(keytips regenerated with cargo xtask keytips).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A small reader for the SYLK text format: cell values (numbers, text,
booleans, errors), formulas converted from R1C1 to A1 as Excel does
(relative/absolute references, ranges, whole rows and columns, shared
formulas) with their cached results, number formats (P / F;P) and
column widths (F;W). Import only; saving as .slk says so. The sheet is
named after the file, and damaged records are skipped with a warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cmd/Ctrl+V dropped egui's PasteImage event, and the ribbon/menu Paste only read
text, so screenshots and images copied in a browser reported "Nothing to paste".
Both now encode the pixels as PNG (bounded to 16384 px per side, 64 Mpx) and run
insert.picture as a floating picture anchored at the active cell, as Excel does.
Text on the clipboard still wins. New host service: Services::paste_image (arboard).
Fixes#699, #3018, #3182.
The bottom edge of the formula bar is a resize handle: dragging it sets
the height in whole lines (up to half the window; one line collapses
the bar). Expanding again (the chevron, Ctrl+Shift+U) returns to the
dragged height instead of a fixed five lines. The drag previews live
and commits once through view.expandFormulaBar {lines}, which is also
scriptable; the height is kept with the UI preferences.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds 43 legacy sequences to keytips.json (Insert, Edit, Format, Data,
View, Tools menus and File > Page Setup), each running the command or
dialog the modern key tip already uses. Alt+I, D, V and T are implicit
levels with no ribbon tab; badges are not painted under them, as in
Excel. The generator checks Options (a Backstage page) by its page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Right-clicking a cell with a link only offered "Link…", whose dialog was
titled Insert Hyperlink, so changing a link wasn't obvious. As in Excel,
the cell menu of a linked cell now offers Edit Hyperlink… (the same
dialog, filled with the link and titled Edit Hyperlink), Open Hyperlink
(as clicking the link: a reference selects its cell, a web or e-mail
address asks first) and Remove Hyperlink (edit.clearHyperlinks). A cell
without a link keeps "Link…".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3238, "Limit the remove_personal_info
doc comment to XLSX saves": only the XLSX writer leaves out the author
and company; the workbook's own serialization still holds them. The
comment now says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three places still split a `Sheet!Name` reference and stripped the
sheet's quotes by hand: the chart resolver (is a series name a defined
name?), the XLSX chart writer (the name's cache) and the macro editor's
example references. The first two left a doubled quote in the name, so
a series named `'Bob''s'!Title` (a name on the sheet "Bob's") wasn't
found and showed that text instead of the name's value; they also split
at the first `!`, so a quoted sheet holding one broke. All three now use
core's split_sheet, the one reading formulas use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up: a column-handle drop on the last frozen column while
scrolled, which fails if freeze_boundary measures with x(c + 1), and a check
that frozen A1's text lies inside its clip, not only that the clip is A1.
Paste Special only knew the letters F, V, T, C and E. Excel gives every
option one: A (All), N (Validation), X (All except borders), W (Column
widths), R (Formulas and number formats), U (Values and number formats);
O, D, S, M, I for the operations; B for Skip blanks; and L for the
Paste Link button, which pastes at once. One table now maps each letter
to what it does; Enter still pastes with the chosen options.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Excel can hide the sheet tabs, the horizontal scroll bar and the
vertical scroll bar of a workbook window (File › Options › Advanced ›
Display options for this workbook), saved with the workbook. GridCraft
already read and wrote these settings (#3057) but always showed all
three, and had no command or option for them.
New commands view.showSheetTabs, view.showHorizontalScrollBar and
view.showVerticalScrollBar {on?} (toggling without `on`, undoable,
saved with the workbook), and a "Display options for this workbook"
section in File › Options. The tab bar follows them: without the tabs
the scroll bar takes the bar, without the scroll bar the tabs do, and
with neither the bar isn't shown; without the vertical scroll bar the
grid takes its width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3223:
- "Skip leading HTML comments before the prefix check": a page starting
with an exporter's <!-- comment --> was read as CSV text. Complete
leading comments are now skipped before looking for the page's start.
- "Preserve nonbreaking spaces in HTML text imports": split_whitespace
turned New York into "New York". The decoded cell text is
used as is (ordinary whitespace is already collapsed), so the
non-breaking spaces stay, as Excel keeps them.
Test: a_web_page_table_opens_after_a_leading_comment_and_keeps_nonbreaking_spaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Excel's per-workbook privacy setting (Trust Center › Privacy Options ›
Remove personal information from file properties on save, set by the
Document Inspector) is saved as <workbookPr filterPrivacy="1"/>, and
while it is on Excel leaves the author and company empty on save.
GridCraft dropped the attribute, so a cleaned workbook came back with
the setting off, and it wrote the author and company back.
Workbook.remove_personal_info now keeps the setting (read and written
as filterPrivacy), and while it is on the writer leaves dc:creator and
Company out. Other properties are written as before. Note authors and
lastModifiedBy are the rest of the issue (the latter overlaps #881).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid took a column's right edge to be where the next column starts,
`x(col + 1)`. At the freeze line the next column is in the scrolled pane
and moves with the scroll, so everything measured that way inside the
frozen columns shrank as the sheet scrolled right:
- text overflowing from a frozen cell was clipped at the scrolled
column's edge, until only a character or two showed;
- the frozen columns' gridlines stopped short, with a stray vertical
line drawn inside them;
- a merged range's borders under the last frozen column ended early;
- the freeze handle measured the last frozen column wrongly when
dropped while scrolled.
Geo gains `x_end`/`y_end` (a column's or row's own far edge) and
`same_col_pane`, and every such measurement uses them. Text overflows
only into empty cells of its own pane. In Page Layout view the
gridlines now stop at each page's last column instead of running into
the margin.
Fixes#2544
Addresses CodeRabbit review on #3226, "Skip hidden corner destinations"
and "Keep merged destinations inside the selected area": with column D
hidden, Ctrl+. on B2:D3 made D2 active; with B1:C1 merged and column C
selected, it made B1 active, outside the selection. A corner in a hidden
row or column, or one whose merged cell starts outside the selected
area, is now passed over like a locked corner on a protected sheet.
Test: next_corner_skips_hidden_corners_and_stays_inside_the_selected_area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3223:
- "Decode HTML before parsing its cells": a page in Windows-1252 read as
lossy UTF-8, so "Café" lost its é. The page is now decoded as a CSV is
(BOM, UTF-8, else Windows-1252/1256); decode_text is exported for it.
- "Keep visible cell text when csv_as_text is enabled": opened as text, a
cell with x:num took the recorded number ("4,50" became "4.5"). The
recorded number is used only when values are read.
- "Preserve the imported format when styling a header": a date in a <th>
lost its date format when made bold and centred. Header styling now
builds on the style the CSV reader gave the cell.
Test: a_web_page_table_keeps_its_characters_text_and_header_formats.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3226:
- "Check the next corner against protected-sheet selection rules": on a
sheet protected without Select Locked Cells, Ctrl+. could make a locked
corner active. It now goes on to the next corner it may select.
- "Keep the active cell at a merged cell's anchor": a corner inside a
merged cell made that cell active instead of the merge's top-left
cell. The corner now lands on the top-left cell, and corners in the
same merged cell are one stop.
Selection::next_corner_by takes where each corner leads (or None to skip
it); next_corner keeps its plain behaviour.
Tests: next_corner_skips_corners_the_sheet_protection_does_not_let_you_select,
next_corner_stops_once_on_a_merged_corner_at_its_top_left_cell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A workbook saved by Excel in R1C1 reference style says so in
<calcPr refMode="R1C1"/>. GridCraft didn't read the attribute, so
opening and saving such a file switched it back to A1 style for the
next person who opened it in Excel. The calculation settings now keep
it (CalcSettings.r1c1, serde default false) and the writer puts it back;
A1 stays the default and isn't written. GridCraft itself still shows
and takes A1 references; the R1C1 display and entry are the rest of
the issue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy wrapped any cell that held a double quote in quotes (and doubled
its quotes), so copying `say "hi"` into another app gave
`"say ""hi"""`. Excel quotes only text with a tab or line break; a cell
with just quotes goes on the clipboard as it is.
Pasting text read a field that starts with a quote as quoted even when
its closing quote doesn't end the field, so `"hi" there` (which Excel
copies as it is) pasted as `hi there`. A field is now quoted only when
its closing quote is followed by a tab, a line end or the end of the
text; otherwise it is the text as written, as Excel pastes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While dragging with a pen, the stroke under the pointer was always
painted blue at 2 pt, whatever pen or highlighter was chosen, so the
preview and the committed ink disagreed (a highlighter previewed as a
thin blue line). The preview now uses the colour and width draw.stroke
stores (pen blue and 2 pt by default, 0.5-40 pt, whole points) and the
translucency Ink shapes are painted with above 8 pt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page margins are kept as f32 and were widened to f64 when written,
so a 0.3-inch margin was saved as left="0.30000001192092896" (and 1.1
as 1.100000023841858). Each margin now goes through its shortest
decimal form first, so the file says 0.3, as Excel writes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Excel's Next Corner of Selection (Ctrl+., Control+. on a Mac) moves the
active cell clockwise to the next corner of the selected area (top-left,
top-right, bottom-right, bottom-left) and keeps the area selected; the
new active cell is also the anchor, so Shift+arrows then move the
opposite corner. GridCraft had no such command.
New command selection.nextCorner (Selection::next_corner) bound to
Ctrl+. on every platform. A one-row or one-column area goes end to end;
a single cell stays where it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A workbook with checkboxes saved by GridCraft made Excel repair it
("Repaired records: Format from /xl/styles.xml (Styles)") and drop every
checkbox. The feature property bag part GridCraft writes for checkboxes
left out extRef="XFComplementsMapperExtRef" on the XFComplements bag,
the bag the cell formats' xfComplement indexes into. With it, Excel
opens the file without repairing and shows the checkboxes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3221, "Check the output size before
with_subtotals builds subtotal lines": the size limit was checked only
after every subtotal line existed. A row with 4000 key columns at total
depth 4000 nested with_subtotals 4000 levels deep and overflowed the
stack (the process aborted) before the late check could refuse it.
with_subtotals now works without recursion: each line gets an id for its
key prefix at every level, a stable sort by those ids keeps each group
together in first-appearance order, and one walk adds the subtotals.
The output size (with the header and total lines when present) is
checked while the groups are counted, before any subtotal line is built.
Test: groupby_subtotals_past_the_size_limit_are_refused_before_they_are_built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3216, "Keep the selection on the sheet it
describes": moving Two!B1:B2 with Sheet1 active selected A1:A2 on
Sheet1, where nothing moved. The selection belongs to the active sheet,
so range.move now keeps it when the moved cells are on another sheet; a
move on the active sheet still selects the moved cells.
Test: drag_and_drop_on_another_sheet_stays_on_that_sheet (now also
checks the selection).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Web apps' "Export to Excel" often writes an HTML table and names it
.xls. GridCraft read such a file as CSV text, so it opened as lines of
markup in column A. Excel opens the table (after warning that the
format and the extension don't match).
Opening a file whose bytes are a web page with a table now reads the
first table as a sheet, through the clipboard HTML table reader: the
cells go through the CSV reader, so values read as they do in a CSV
(and stay text with csvAsText); <th> cells are bold and centred; the
sheet takes the page's <x:Name> or the file's name. A page that isn't
named .htm/.html opens with a warning saying what it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3219, "Recognize the legacy Count
Numbers caption when the function changes": workbooks saved before
Count Numbers was captioned "Count of" carry "Count Numbers of Val"
(optionally with a number suffix). pivot.valueSettings now treats that
as an automatic caption of a Count Numbers field, so changing the
function renames it; a custom name stays.
Test: legacy_count_numbers_caption_follows_the_function.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3216:
- "Pass the resolved sheet to the paste step": a sheet-qualified move
(`Two!B1:B2` with Sheet1 active) was checked on Two but pasted on the
active sheet, replacing its data unasked. range.move now calls
paste_special with the range's sheet.
- "Check spilled values in the drop destination": cells showing a dynamic
array's spilled values hold no cell of their own, so the drop replaced
them without the prompt. They now count as data, as in Excel
(ISBLANK of a spill cell is FALSE, COUNTA counts it).
Tests: drag_and_drop_on_another_sheet_stays_on_that_sheet,
drag_and_drop_asks_before_replacing_spilled_values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GROUPBY's total_depth only ever added one Total line. In Excel a depth
of N adds a subtotal line for each group of the first N - 1 key columns
(after the group, or before it when the depth is negative) and labels
the last line "Grand Total"; a depth greater than the number of key
columns, or above 1 with field_relationship 1 (a flat table), is
#VALUE!. The subtotals aggregate the group's source rows, so AVERAGE and
COUNT subtotals are right too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Show Values As › Rank Largest to Smallest counted every larger sibling,
so ties skipped ranks: 5, 4, 3, 3, 4 ranked 1, 2, 4, 4, 2 where Excel
gives 1, 2, 3, 3, 2. It now counts the distinct larger values.
A new Count Numbers value field was captioned "Count Numbers of Units";
Excel captions it "Count of Units", as GridCraft's XLSX reader already
names a value field saved without a name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A workbook with an in-cell picture and a dynamic-array formula wrote
the XLDAPR futureMetadata extension URI in upper case
({BDBB8CDC-…}), and Excel refuses such a workbook. The metadata part
written for dynamic arrays alone, which Excel opens, spells the same
URI in lower case; the rich-value writer now does too.
Together with #1533 (content types and the XLRICHVALUE URI) this lets
Excel open a workbook with an in-cell picture and a spilled formula.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dragging the selection's border onto cells that hold data replaced them
without asking, so dragging column B onto column A silently destroyed A.
Excel asks "There's already data here. Do you want to replace it?" first
(its "Alert before overwriting cells" option, on by default).
The drop now runs a new `range.move {range?, to, overwrite?}` command:
a cut and paste that returns a confirm question when the destination
holds data other than the moved cells; the grid shows it with OK and
Cancel, and OK runs it again with `overwrite: true`. A refused move
leaves the sheet and the clipboard as they were.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sheet.moveColumns resolved its target sheet from `sheet`, or a
`Sheet!` range or cell, but its two protection checks were passed
only the `sheet` parameter, so a qualified range moved columns of
a protected sheet while the checks authorized the active one. Both
checks now get the resolved sheet index.
- check_protection compared only cell content, so a sort or duplicate
removal moving an unlocked cell with equal content onto a locked one
left the content as it was but made the cell editable. The commit
check now also refuses a change that leaves a previously locked cell
unlocked (effective lock, row and column formats included).
A source that takes in the cell being checked (=$A1:$A2 on A2:A3 is A2:A3 at A3) listed the value being typed, so any new value passed. The list is now read from the workbook as it was before the entry, as in Excel.
A list source such as =$B1:$B2 is written for the rule's top-left cell.
list_items evaluated it there for every cell, so the dropdown and the
check showed and accepted the top-left cell's items everywhere. It now
takes the cell and shifts the source to it, as Excel does.
Clearing validation from cells (or setting a new rule, or pasting a
validation over them) and Clear Rules on selected cells shrink a rule's
areas. When that removes the rule's top-left cell, the relative formulas
were left written for the old cell, so the cells left checked other
cells: clearing A1 off a custom `A1>10` on A1:A9 made A2 check A1.
Rewrite the relative references of validation formulas, and of
conditional format expressions and comparison values, for the new
top-left cell. Absolute references and literal lists stay as they are.
Since #2729 the ribbon picks a tooltip's shortcut by the OS egui reports
(`ctx.os()`), which egui_kittest pins to Nix unless told. The test still
chose the expected text with cfg!(target_os = "macos"), so on a Mac it
looked for "AutoSum (⌘⇧T)" while the harness drew "AutoSum (Alt+=)",
and `cargo xtask ci` failed there while the Linux gate stayed green.
The harness now pins macOS and the test expects the ⌘⇧T tooltip on every
host; widgets::tests already covers the Windows/Linux "Alt+=" text.
Verified on macOS 27 (arm64): `cargo xtask ci` passes all 10 steps (with
CRAFT_FONTS_DIR at the gate's craft-fonts commit); on main (7bb397a3)
it fails at test with "tooltip with the shortcut".
Excel writes the relative formulas of a multi-area rule for the top-left
cell of the whole sqref (lowest row, lowest column), whatever order the
areas are listed in. GridCraft anchored them on the first area listed, so
a validation on `H62:H68 H26:H27 H93:H95` with `MOD($H26,$F26)=0` checked
row 26 - 36 at H26 and refused valid input.
Add `RangeRef::top_left` and use it in validation checks, list sources,
pasting a validation, conditional format lookups and the formulas the
xlsx writer generates for text, blank and error rules.
The size box draws its value with a frameless TextEdit. The theme floors
a text edit at interact_size.y (22 pt), and egui's text edit style aligns
its text to the top of that, which wins over the child layout's
Align::Center. The digits sat 3.5 pt above the middle of the 24 pt box,
4 pt above the Font name's baseline. The text edit now aligns its text
LEFT_CENTER.
The new test checks that the value is centered in its box and on the
Font name label's baseline. It fails without the change.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`parse_streaming` counts every element it attaches to a parent against
MAX_XML_NODES, but the cells of a worksheet are attached to their `row`
while the row is open, and the row is then handed to the callback and
dropped. So the count grew with every cell of a sheet although the tree
never held more than one row, and a workbook with a sheet of about one
million ordinary cells (12,274 rows by 103 columns) was reported damaged
after 6,310 rows since #2413. Shared strings, streamed by `si`, had the
same exposure: a table of about a million plain strings, or fewer
rich-text ones.
The count now returns to its value at the start of a split element when
that element is handed out, so a streamed part of any size passes while a
part that keeps a million elements in its tree is still refused.
Regression test: 5,001 streamed rows of 100 cells are read whole; the same
part kept in a tree is refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
link.open is reachable by the control channel, CLI, MCP and workbook
scripts, so it could open https://attacker/?d=<cell data> without the
user seeing it. By default (LinkPolicy::Confirm) it now refuses web and
e-mail addresses with an error and opens no dialog; references still
navigate and other schemes stay refused. A host may opt in with
LinkPolicy::Allow (Rust API only, no command sets it).
The UI's Open Hyperlink prompt no longer runs a command on OK: it calls
Session::open_confirmed_link with the address it showed, kept in a
private field, so ui.dialog.confirm can't press OK and ui.dialog.set
can't swap the address behind the prompt text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>