On macOS five of Word for Mac's default shortcuts were missing or unreachable:
- Cmd+Shift+S is Save As (it opened Apply Styles, which keeps Ctrl+Shift+S
on Windows and Linux).
- Cmd+Option+E inserts an endnote. macOS takes Cmd+Option+D to show and
hide the Dock, so the old key never reached the app.
- Cmd+Option+A adds a comment (Cmd+Option+M still works).
- Control+H opens Find and Replace (Cmd+H still works).
- Cmd+Option+R collapses or expands the ribbon (ui.collapseRibbon).
Keys on Windows, Linux and the web are unchanged. Tests pin the bindings
on each platform.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* contributors: add Nael-Nathanael
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* insert.picture and insert.icon leave the caret after the picture (#965)
A script that typed right after inserting a picture replaced it, so the
picture was missing from the saved file. Commands now leave the caret
after the new picture; Picture Format commands still act on it. The app
still selects a picture the user inserts, as in Word.
Fixes#965
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Select a pasted picture again, keep pictures inserted in a row (#965)
Desktop paste inserts through execute, so after the caret change it
left a pasted picture unselected. WordApp::select_inserted_picture
selects the picture just before the caret; run and the paste path call
it, paste once after the last picture so a multi-file paste keeps them
all. Tests for paste selection and for two inserts in a row.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
New parts, lists and comments took "largest id + 1": after an id of
u32::MAX (a .json model or a .docx can carry one) that overflowed, a
panic in debug builds and a wrap to 0 in release, and list.define's
saturating_add reused the taken id. free_id gives one past the largest,
or the lowest free id once that would overflow; try_add_part,
try_add_list and try_add_levels report ids running out and add nothing,
and the commands that add parts and lists use them.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Paragraph::normalize now uses saturating sums, moves a run end that splits
a character to the next boundary, and makes the runs cover the text
exactly. Opening a .json document normalizes every paragraph in every story.
Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Crop handles: Picture Format › Crop crop mode on the canvas, picture.crop takes the new size
ui.crop toggles crop mode for the selected (unturned) picture: black crop
handles trim a side or two, keeping the scale and the visible part in place;
the cropped-away part shows dimmed; Esc, Enter, a click outside or Crop again
keep the crop. A drop is one picture.crop with width/height, one undo step;
a floating picture moves by what a left/top crop trims.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Crop handles: flipped pictures trim the side under the handle, stale drags drop unapplied
Review round 1: the crop maths, the dimmed preview and the floating
position shift map the on-screen side to the source side (flip H swaps
left/right, flip V top/bottom). A drag remembers its picture and is
dropped unapplied if that picture is no longer the one in crop mode.
Sizes stay within 1-4000 pt at the same scale. Pointer-path tests drive
a real handle drag and a click outside. The dim is darker, and the
canvas keeps following a crop drag's pointer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs after rebasing on main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Crop handles: ui-parity row merged with main; #1242's Cell Size note moved to the Tables row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #967: para.keepNext, keepLines, pageBreakBefore, widowControl document {"value"?: bool}
The four commands already set the property when given a bool value and
toggle without one, but listed their params as {}. List them as format.bold
does and pin the behaviour with a test.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #967: test that the params mention value, not the whole string
Other toggles annotate it, e.g. "(omit to toggle)"; a later hint should not
break this test.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Over differently formatted paragraphs the dialog showed the caret
paragraph's values and OK gave them to every selected paragraph, through
para.rtl/ltr, para.align and para.set (three undo steps). It now reads
every selected paragraph: a field they share shows as before, a field
they differ in shows blank (number boxes empty, alignment empty, check
boxes neither on nor off, no direction picked) and OK leaves it as each
paragraph has it until the user sets it. OK sends only the fields that
changed, as a single para.set.
para.set takes "onPage": alignment and indents as seen on the page,
which each paragraph turns into its own start and end once `props` is
applied, so mixed directions get the right sides in one command. The
line spacing and space boxes no longer pull an out-of-range value from
the document (line spacing 6) into their range, which counted as an
edit.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
File Explorer's New › Microsoft Word Document makes a zero-byte .docx.
file.open read it as a Word package, failed, and left the untitled
document in place, so Save asked for a name. An empty file (from disk or
as bytes) now opens as the Normal-template blank document File › New
gives (region's paper, editing language) and keeps its path.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Insert › Link › Place in This Document: link.targets, links to bookmarks, headings and the top (#1234)
* Insert Hyperlink: Link to › Place in This Document (#1234)
The Insert Hyperlink dialog gets Word's "Link to:" choice. Place in This Document
lists the top of the document, its headings (indented by level) and its
bookmarks, and links to the chosen one; the text to display follows the choice
until it is typed over. It uses insert.link with `top`, `heading` or `bookmark`
and link.targets for the list.
* Docs: update the progress docs for this change
* Docs: put the new sentence in the WordCraft / evidence column
* i18n: Insert Hyperlink link-target strings in every catalog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: read Word's VML text watermark from DOCX headers
Word's Design › Watermark is a VML shape with a v:textpath in the headers.
The reader dropped it, so screen and PDF showed no watermark. It now maps
it to the document's watermark model (text, font, fill colour,
semitransparent from v:fill opacity, diagonal when turned), which the
renderers already draw. Only Word's watermark shape (PowerPlusWaterMarkObject
id) or text centred on the page both ways counts; other header WordArt,
body WordArt and shapes inside text boxes are left alone. Writing it back
to DOCX is still missing (it was already lost on save).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: write the watermark to DOCX as a VML text shape in the headers
The DOCX writer dropped settings.watermark, so a watermark read from Word
(or set in WordCraft) was lost on save. Every header part now starts its
first paragraph with a VML shape (ECMA-376 Part 4) whose v:textpath carries
the text, font, fill colour, semitransparency (v:fill opacity) and diagonal
turn, centred on the margins behind the text, with the reader's
PowerPlusWaterMarkObject id so it reads back as the watermark, not a shape.
The shape type is our own: a straight path the text follows, fitted to the
box. Slots the pages show with no header of their own or linked from an
earlier section (default; first page with a title page; even pages with
even/odd headers) get a header holding only the watermark; linked sections
stay linked. A blank watermark isn't written. Check Compatibility no longer
warns about watermarks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: give the watermark's text path a top and a bottom edge so Word draws it
With a single line as the shape type's path, Word loaded the watermark as
WordArt (right text, size and turn) but drew nothing. Checked black-box in
Word: diagonal, horizontal and header-less documents now show it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: test the watermark through a Word-shaped file, table-only and even-page headers
Read → write → read of a file with Word's watermark keeps the watermark, the
headers' text and paragraphs and one shape per header; header WordArt that
isn't a watermark doesn't become one. A header holding only a table gains no
line; with odd and even pages an even header is added and later sections
stay linked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: read VML freeforms (v:shape with a path) as freeform shapes
Legacy VML shapes with their own `path` (ECMA-376 Part 4), such as rule
lines in headers, were dropped on open and lost on save. They are now
read as freeforms: m/t/l/r/c/v/x/e commands in the coordsize/coordorigin
space, nf/ns, v:fill and v:stroke opacity; arcs drawn straight, formula
references read as 0. Saved as DrawingML custom geometry.
Tests: header rule read and round-tripped; hostile paths and coordinate
spaces stay finite and never panic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: VML freeforms whose path uses formulas aren't drawn
Formulas (@n, from v:formulas) aren't evaluated; reading them as 0 drew stray lines. Parity row states the fill-opacity limit (none at 0, otherwise opaque, as for DrawingML fills).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: VML freeforms reuse the watermark's vml_fraction
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: move the VML freeform reader out of this PR
The three VML freeform commits (b7a6cc7, d8fa665, 945cbb4) aren't part of
#1290; they go to their own pull request so this one stays on the
watermark.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #1077: forms protection fills content controls (w:edit="forms")
* #1077: forms walk stays conservative for unclosed control markers
* #1077: forms protection edges — nested locks, orphan markers, tables, empty interiors
* #1077: forms protection — formFields state and Find Next Region over fillable controls
* #1077: forms protection round-trip, password guard, docs
* #1077: final review fixes — lock-param gate, per-list pairing, docs, pane perf
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Both signature_contradicts and open_by_content defined identical local
ZIP and OLE magic number consts. Extract them to module-level ZIP_SIG
and OLE_SIG with doc comments so there is a single source of truth.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Layout: inside/outside floats swap sides on even pages; Page Setup shows mirror margins (#1181)
Floats aligned inside or outside, or placed relative to the inside or
outside margin, were always put as on an odd page, so shapes Word puts
in a book's outside margin all came out on the right. They now follow
the page they're on (ECMA-376 ST_AlignH, ST_RelFromH): on an even page
the inside is the right side. Floating tables (w:tblpXSpec) too.
With mirrored margins, even pages are laid out as odd ones and then
shifted; float positions now allow for that shift, so floats placed
from the page edge stay put (the "known limit" in #452) and the left
and right margin areas are each page's own.
Mirrored margins read from a file were invisible in the UI: the Margins
menu ticks Mirrored when they're on, and Page Setup gets a Mirror
margins check box (labelling the margins Inside and Outside), applied
through a new `mirror` param of layout.margins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Mirror margins in every catalog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: lloyd <lloyd@stackworx.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
#1202's test selected all once with the caret in a table; since #1478,
Select All in a table grows a step at a time (cell, row, table,
document), so one press selected only the cell and the table stayed.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Insert › Bookmark: Delete and Go To, laid out as Word's (#1236)
The Bookmark dialog had only Add: a bookmark could not be deleted, and a
click in its list jumped to the bookmark at once. It is now laid out as
Word's (observed in Word 2019 on Windows): the name box over the list, Add,
Delete and Go To beside them, Sort by Name or Location and Hidden bookmarks
below. A click in the list picks a name; Add (Enter) adds and closes; Delete
and Go To leave the dialog open, and after a delete Cancel reads Close. The
dialog opens on the bookmark around the caret, and Add is off for a name
the command would refuse.
insert.deleteBookmark {name} removes a bookmark's start and end wherever
they are, also across paragraphs, as one undo step, keeping the selection
on its text; from the command search without a name it opens the dialog.
Adding a bookmark under a name in use goes through the same removal: it
looked for the old end only in the start's paragraph, so a bookmark across
paragraphs left a stray end behind. edit.goto with a bookmark selects its
text, as Word's Go To does, instead of placing the caret at its start.
Closes#1236
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert › Bookmark: Ctrl+click on a link to a bookmark keeps a caret (#1236)
Word's Go To selects a bookmark's text, but Ctrl+click on a link to a
bookmark only puts the caret at the bookmark's start (checked in Word 2019
on Windows). Following a link runs edit.goto, which now selects, so it
collapses the selection to its start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Resolve the doc conflict markers left by the previous merge
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Table Layout: the Cell Size boxes apply to the whole selection (#1242)
table.rowHeight and table.columnWidth read the caret's cell only. With the whole
table selected the caret is in the last cell, so only the last row's height and
the last column's width changed.
Both now use the cells the selection covers (selected_cells): every selected
row gets the height and every grid column the selected cells touch gets the
width, as one edit. Without a selection that spans cells they still change the
caret's row and column.
* Docs: update the progress docs for this change
* Docs: put the new sentence in the WordCraft / evidence column
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The match arm included | "" but ext.is_empty() is already handled above
by the guard that routes empty extensions to open_by_content. This branch
could never be reached, making it dead code that misleads readers.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Right-click on a table of contents entry offers Update Field (part of #1197)
* Test: the TOC context menu check allows more than one Paste item
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Word forms keep their legacy form fields and rows' skipped grid columns when saved (part of #1174)
* Tests: field initializers name the new form field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
failed_pruning_cannot_push_a_record_beyond_the_discovery_limit failed
once in a full workspace run: a record whose lock is briefly held (by a
child another test is spawning, which shares the descriptor until it
execs) is skipped rather than reported, so scan() returned Ok. The test
now retries for up to a second before asserting.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M10: retain local crash recovery on current main
* i18n: Backstage Recover in every catalog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
docbin decrypts XOR-obfuscated, RC4 and RC4 CryptoAPI files after verifying the
password against the stored verifier; the engine reuses the .docx password
prompt errors. Written from [MS-DOC] and [MS-OFFCRYPTO]; synthetic tests only.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #778: .xls and legacy-encoding recipient lists; filter and sort recipients
- Recipient lists: legacy Excel 97-2003 .xls (BIFF8 reader on the cfb crate already in the
tree); Excel 5.0/95 and password-protected .xls refused with a clear message; a CSV named
.xls reads as text, an HTML one gets an error. .xls added to the picker filters (desktop,
web, built-in browser) and drop handling.
- Text lists (and plain-text import) detect legacy encodings when not UTF-8/UTF-16: GBK/GB18030,
Big5, Shift_JIS, EUC-KR, Windows code pages (encoding_rs + chardetng, MIT/Apache); an
`encoding` param overrides.
- mailings.filterRecipients / mailings.sortRecipients: field, comparison (equal, notEqual,
less, greater, lessOrEqual, greaterOrEqual, blank, notBlank, contains, notContains), value,
And/Or joins; sort keys. Preview, Finish & Merge and labels use only the matching records
in sort order. Edit Recipient List edits every record and keeps the filter.
- Recipient List dialog: Filter and Sort section and a merge-order column; catalog rows;
strings in every catalog; docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Hungarian and French strings for this change
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #885: total order for recipient sort, Bulgarian strings
- compare_cells now ranks numbers (compared numerically) before text (compared
case-insensitively), so it is a total order; a column mixing 9, 10 and 1a
could make slice::sort_by panic. Regression test sorts mixed columns.
- Bulgarian catalog: the 24 Filter and Sort strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* vi: mail merge Filter and Sort strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #775: STYLEREF fields in headers and footers show each page's result
Running headers/footers with STYLEREF (e.g. the current chapter title) are
evaluated per page at layout time (ECMA-376 §17.16.5.59): the page's first
paragraph of the style (\l: last), else the nearest before, else after.
Style names/ids, heading levels (STYLEREF 1), character styles, and the
\n \r \w \s \t \p switches. Body STYLEREF fields update with the other
fields. STYLEREF added to Insert › Quick Parts › Field with its options.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #775: STYLEREF error text in our own words
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #882: translate the STYLEREF Field dialog strings
Adds the style picker label, the six STYLEREF switch labels and the
STYLEREF description to all 18 interface catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* STYLEREF: reuse the style index while the body and styles are unchanged
Headers with STYLEREF rebuilt and hashed the body's style index on every
layout pass (echelon's follow-up note on #882). The layout cache now keeps
the index with the body blocks it came from (shared, so an edited block is
a different pointer) and the styles/numbering hash, and rebuilds it only
when either changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* vi: STYLEREF field strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* test(engine): drop a needless drop() that clippy 1.97 rejects (drop_non_drop)
* Comments: a click inside a selected balloon moves the caret in its text (#1013)
- The canvas under the markup area also "contains" the pointer, so a press on the balloon counted as a click on the page and left it
- Presses over a balloon card now belong to the card, in both the deselect check and the document click in mouse()
- Test: press, drag and release inside the selected balloon; a click on the page still leaves it
* Comments: an option to show only the selected text's comment (#1055)
- Track Changes Options › Comments: "In the margin" (default, as in Word) or "Only for the selected text"
- The second mode takes comments out of the margin column; the comment under the caret, or the clicked one, floats beside the page, over its edge in a narrow window
- The floating card has its own foreground layer, so presses on it never reach the canvas
- review.trackingOptions takes commentDisplay: margin|selected
- comment_range_at_caret: the comment whose range holds the caret
- Strings in all catalogs; tests for the option, the helper and the card's clicks
* vi: comment display options
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: merge settings.xml into the original instead of regenerating it (#1034)
A save regenerated word/settings.xml from the model, so every setting the
model doesn't hold was dropped: compatibility options (lost in 18 of 18
real-world Word files), rsids, document variables, proofing state and the
colour scheme mapping.
The reader now keeps the original part when it holds such settings, and
the writer merges into it: the original root and every unowned child stay
byte for byte; owned children follow the model in CT_Settings order; in
w:compat only compatibilityMode is replaced. The privacy options are the
model's since #1062, so they are owned too. Kept children that point at a
relationship (a mail-merge data source) are dropped. Anything unexpected
falls back to the generated part. Files WordCraft wrote itself keep
nothing extra.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: carry unmodelled parts and keep comment ids, style defaults, latent styles, list ids and app props (#1034)
Builds on the settings.xml merge.
- Parts the package or main document relate to that WordCraft neither
models nor writes (webSettings, stylesWithEffects, people, add-in task
panes, the thumbnail, future parts) are carried verbatim with their
relationships and content types; relationship types the writer
produces, and any the body refers to by id, are not (HANDLED_RELS).
- commentsIds / commentsExtensible: durable ids and UTC dates are read
into the model and regenerated against the written paragraph ids.
- w:default on localized default styles, w:latentStyles, lists'
nsid/tmpl/multiLevelType, and app.xml Template/Company/Manager/
HyperlinkBase round-trip; counts are not carried, and Company/Manager
are dropped when the file asks to remove personal information.
- Comment reference marks use the document's "annotation reference"
style id instead of a hard-coded CommentReference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
File › Options › Proofing gets a Dictionaries folder: a folder of Hunspell
.aff/.dic files searched before the system locations, saved with the settings
and applied at start-up. An app started from Finder or the Dock doesn't see
WORDCRAFT_DICTIONARIES, so this is how it finds a dictionary kept in a folder
of the user's own. Changing it reloads the Polish and Portuguese dictionaries
and checks the text again. The pane lists the dictionary file each language
found, or that none was found, so missing spelling marks are explained.
Agents set it with file.options {"dictionariesFolder": path | "auto"}; a
missing folder or a relative path is refused and changes nothing.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Clippy 1.96: simplify boolean guards, drop a needless drop()
Rust 1.96's clippy flags nonminimal_bool in find_pattern, table_mouse and
table_resize, and drop_non_drop in a ribbon test; `cargo xtask ci` failed at
clippy on main. Same logic, rewritten as clippy suggests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UI: mini toolbar steps aside for the ribbon
The mini toolbar covered the ribbon's drop-downs (the font size list opened
under it). A press outside the document and the bar (the ribbon, a pane, the
rulers, the status bar) now puts the bar away until the selection changes or
the next press in the document; presses on popups (the bar's own lists, an
open ribbon list over the page) neither hide nor restore it. The bar also
moves from the Tooltip layer to Middle, so every popup and menu draws over
it, as its comment always said.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UI: mini toolbar scrolls under the ribbon, not over it
With text selected, scrolling the selection up toward the ribbon carried the
mini toolbar along on top of the rulers and ribbon. The bar is now clipped to
the document area: it slides under the rulers and ribbon (or the status bar)
with the text and goes once none of it shows. Its area is constrained to the
document for hit testing too, so the hidden part no longer swallows clicks
and drags meant for the ruler or the ribbon underneath.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* feat(ui): implement Ctrl+drag to copy objects on canvas
* feat(ui): implement Ctrl+drag to copy objects on canvas and update translations for "Copy to"
* vi: Copy to
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Down from the start of the line above a table (or Up from the start of the
paragraph after it) skipped the whole table: `DocLayout::vertical` took the
nearest line covering the caret's x, and a cell's lines start a cell margin in
from the table's edge, so the paragraph beyond the table won. Lines between
the caret's paragraph and that line in document order now come first: the
caret goes into the nearest such line's table row, to the cell under it or the
horizontally nearest. Another cell of the caret's own row never counts, so Down
in a table still goes to the cell below. Pinned in `tests_typing.rs` and
`docs/typing-parity.md`.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Reject All (and Accept All of a whole deleted paragraph) joined the next
paragraph into the emptied one, so the next paragraph took the removed
paragraph's properties: a § heading after a rejected list item became a
list item and later § numbers shifted. An emptied paragraph is now removed
whole, as Word does. Found while round-tripping a Word-saved contract.
Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M10: enable document renaming and account settings
* no-mistakes(review): Allow case-only document renames without overwriting files
* no-mistakes(review): Keep unsaved rename as session file name, not Title
* no-mistakes(review): Keep AutoSave and Recent across renames; Compare clears name
* no-mistakes(document): Document file.rename write-root rule; dedupe rename gap note
* vi: Rename Document strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* #916: Cross-reference opens a dialog
The ribbon's Cross-reference button ran insert.crossReference with no
target, which only returns the target list, so nothing happened. It now
opens a Cross-reference dialog: reference type (heading, bookmark,
equation, figure, table), what to insert (text, number, page,
above/below) and the item, ending in insert.crossReference. Scripts and
agents still get the target list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Cross-reference dialog strings in the audited catalogs (ru, hu, tr, bg)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Cross-reference dialog strings in pl, it and vi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Tables: AutoFit in a nested table fits within its cell (#971)
A double-click on a nested table's column border let the table widen up to
the page's right margin, far past the cell around it. It now widens only up
to the right edge of that cell's text (a right-to-left table's start margin
on the right); the columns right of the border still keep their widths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tables: a selection through a table shades its rows' whole cells (#971)
A selection from text into or across a table takes whole rows (#722), but
only the letters in the cells were highlighted. Every row it takes from start
to end now shades all its cells (an empty one too), as selected cells do, and
the letters in them aren't shaded twice (`table_mouse::selection_rects`, used
by the canvas). A selection that starts and ends in one table keeps its text
highlight, so selecting all the text of a cell doesn't look like selecting the
cell; a nested table in a shaded row shades with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Typing: Shift+Right at a cell's end selects the cell first (#971)
Shift+Right out of the cell the selection is in selected that cell and the
next one at once. As in Word, the first step out of the cell now takes the
cell alone (its end-of-cell mark) and the next press goes on into the next
cell; in an empty cell the first press selects it. Shift+Left in
right-to-left text, the forward step there, does the same. Pinned in
`tests_typing.rs` and `docs/typing-parity.md`; progress docs for all three
table fixes of #971.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
#1527's engine test needs a system Turkish Hunspell dictionary; on
machines without one it failed the workspace tests.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* proof: Turkish spelling, suggestions, grammar and hyphenation
* Turkish: dictionary tests skip when no Turkish Hunspell dictionary is installed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: read and write tracked changes (gap 19)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
# crates/engine/src/tests.rs
# crates/formats/src/model.rs
# crates/formats/src/odt.rs
# docs/file-format-parity.md
# docs/gaps.md
* ODT tracked changes: deleted paragraph marks don't take the body's insertion; one region per deletion
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Lift equation color from run-level OMML to CharProps during read,
push it back to runs during write. Keep source OMML until the color
changes to preserve control properties the model can't hold.