Skip to content

fix: pending style applies only at its caret; plain paste uses typing style; notify IME on undo/redo - #40

Merged
michaelstonis merged 4 commits into
mainfrom
housekeeping/pending-style-and-paste
Oct 21, 2026
Merged

michaelstonis merged 4 commits into
mainfrom
housekeeping/pending-style-and-paste

Conversation

@michaelstonis

Copy link
Copy Markdown
Contributor

Summary

  • The pending style follows the caret. A format picked with nothing selected (for example Bold at a bare caret) now applies only at the caret where it was picked.
    • Moving the caret discards it: arrow keys, Home/End, a click, or setting the selection from code. The toolbar then stops showing it.
    • Enter still carries it into the new paragraph.
    • New API: FormattingEngine.PendingStylePosition / FontFormattingEngine.PendingStylePosition and DiscardPendingStyleIfMoved(doc, selection).
  • Plain-text paste formatting. EditorInputController.HandlePasteAsync now uses the same style as typing: the pending style, otherwise the caret's formatting. Before, it used TextStyle.Default. ClipboardOperations.PasteAsync now also takes the caret's formatting.
  • IME after undo/redo. MintedEditorView.Undo() and Redo(), from the toolbar and the public API, now call NotifyImeSelectionChanged(), as the Ctrl+Z path already did. Without this, Android and iOS keyboards could keep editing stale text.

Tests

  • New PendingStyleTests with 19 cases. They cover:
    • the pending style being dropped after a key press, a click or a selection set from code;
    • the pending style kept at the same caret, including across a run boundary;
    • the toolbar's state;
    • paste styling: the caret's style, a pending bold or font size, pasting over a selection, and ClipboardOperations.
  • Two CharacterFormattingTests relied on a pending style picked away from the caret. They now pick it at the caret.
  • Core build: 0 warnings. Core.Tests pass (1124) and SkiaSharp.Tests pass (33). The MAUI shared code compiles. The IME change is only compile-checked.

Generated by Claude Code

…tified on undo/redo

A format picked at a collapsed caret (e.g. Bold with nothing selected) kept
applying wherever the user typed next, even after moving the caret. The
FormattingEngine and FontFormattingEngine now record where the pending style
was picked (PendingStylePosition) and DiscardPendingStyleIfMoved drops it once
the selection is no longer that caret (compared by paragraph and character
offset, so run-boundary positions match). The input controller discards stale
pending styles after handled keys, pointer input and before typing, pasting or
Enter (which still carries the style into the new paragraph); the toolbar and
MintedEditorView's formatting state do the same, so moving the selection from
code is covered too.

Plain-text paste inserted TextStyle.Default. HandlePasteAsync now uses the same
insertion style as typing (pending style, else the caret's style), and
ClipboardOperations.PasteAsync inherits the caret's style.

MintedEditorView.Undo()/Redo() (toolbar buttons and public API) did not tell
the platform IME that the text and selection changed, unlike the Ctrl+Z path;
they now call NotifyImeSelectionChanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197tHRWhTiagB1MTsDuPkoa
Copilot AI balanced review requested due to automatic review settings October 21, 2026 17:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Pending-style validation still mishandles origin ranges and deletion-related caret or selection changes.

3 open issues
What changed in this PR

This PR aligns pending formatting and plain-text paste with caret-based typing behavior, and synchronizes platform keyboards after undo/redo.

Changes:

  • Tracks pending styles by caret position.
  • Makes plain-text paste inherit insertion formatting.
  • Adds regression tests, documentation, and IME notifications.
File Description
tests/​MintedTextEditor.Core.Tests/​PendingStyleTests.cs Tests pending styles and paste formatting.
tests/​MintedTextEditor.Core.Tests/​CharacterFormattingTests.cs Anchors pending formats at the test caret.
src/​MintedTextEditor.Maui/​MintedEditorView.cs Refreshes formatting state and notifies IME after history changes.
src/​MintedTextEditor.Core/​Toolbar/​ToolbarRenderer.cs Discards stale styles before updating toggles.
src/​MintedTextEditor.Core/​Input/​EditorInputController.cs Validates pending styles and shares insertion formatting.
src/​MintedTextEditor.Core/​Formatting/​FormattingEngine.cs Tracks character-formatting caret anchors.
src/​MintedTextEditor.Core/​Formatting/​FontFormattingEngine.cs Tracks font-formatting caret anchors.
src/​MintedTextEditor.Core/​Editing/​ClipboardOperations.cs Applies inherited formatting to pasted text.
docs/​formatting.md Documents caret-scoped styles and paste behavior.
CHANGELOG.md Records formatting and IME changes.

Review effort: Balanced

Comment thread src/MintedTextEditor.Core/Formatting/FormattingEngine.cs Outdated
Comment thread src/MintedTextEditor.Core/Formatting/FormattingEngine.cs Outdated
Comment thread src/MintedTextEditor.Core/Input/EditorInputController.cs
Finding 1: TextRange.Empty was default(TextRange), whose positions have cell
coordinates 0/0 (so IsInTableCell is true), while the caret starts at -1/-1.
ToggleBold(doc, TextRange.Empty) followed by typing at the initial caret
therefore discarded the pending style. TextRange.Empty now holds explicit
(0,0,0) positions, and the pending-style comparison ignores cell coordinates
outside table blocks, so default(TextRange) works too; cells in a table stay
distinct.

Finding 2: the comparison resolved both the saved and the current position
against the edited document, so Backspace at the end of a run clamped the
saved offset to the new caret and the pending style survived. The engines
now store the paragraph and character offset when the pending style is
picked (PendingStyleAnchor) and compare the caret against that; Enter
re-anchors it in the new paragraph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197tHRWhTiagB1MTsDuPkoa
… key

Finding 3: HandleKeyDown only checked the pending style after the key. With
Bold picked at offset 5 and the selection then set to [5,8] from code,
Backspace or Delete collapsed the selection back to offset 5, so the check
afterwards saw the original caret and kept the stale pending style. The
controller now also discards it before handling the key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197tHRWhTiagB1MTsDuPkoa
@michaelstonis
michaelstonis merged commit 4c609d0 into main Oct 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants