Skip to content

fix: InsertHyperlinkCommand uses DisplayText; undo/redo of a linked selection - #28

Merged
michaelstonis merged 4 commits into
mainfrom
housekeeping/hyperlink-display-text
Oct 21, 2026
Merged

michaelstonis merged 4 commits into
mainfrom
housekeeping/hyperlink-display-text

Conversation

@michaelstonis

Copy link
Copy Markdown
Contributor

Summary

InsertHyperlinkCommand.DisplayText was documented as the link's display text, but Execute never read it. It now works:

  • DisplayText set: a link with that text is inserted at the caret. If text is selected, DisplayText replaces the selection and becomes the link, like the "Text to display" field in most editors. A DisplayText that is only spaces counts as empty.
  • DisplayText empty: behaves as before. The selection becomes the link, or the URL is inserted as the link text when nothing is selected.
  • Undo: each insert is one undo step, and the caret ends up after the link.

Also fixed: after undoing a link made from a selection, redo threw ArgumentOutOfRangeException, and the undo had already dropped the original text's formatting. InsertHyperlinkAction now saves the affected paragraphs before inserting and locates its range by character offsets, the same way DeleteRangeAction works. That fixes both problems and also restores paragraphs that a multi-paragraph selection merged. The existing 3-argument constructor and HyperlinkEngine.InsertHyperlink overload still work, and there is a new overload that takes displayText.

Testing

  • 5 new tests in CommandTests, each also checking undo:
    • display text with no selection;
    • display text replacing a selection, with undo and redo;
    • display text replacing bold text, where undo brings the bold back;
    • no display text with a selection;
    • no display text and no selection, with both empty and spaces-only display text.
  • Core.Tests and SkiaSharp.Tests pass. The SkiaSharp Release build has 0 warnings, and the MAUI shared code compiles cleanly.

Not included

  • RemoveHyperlink is still not recorded for undo.
  • Inserting a link inside a table cell throws.

Both are noted as follow-ups.

Checklist

  • CHANGELOG entry under Unreleased → Fixed
  • docs/images-hyperlinks.md updated

Generated by Claude Code

DisplayText was documented as the link's display text but Execute never
read it. When set (not empty/whitespace) the link now uses that text,
inserted at the caret or replacing the selected text; when empty the
selection is linked, or the URL is inserted as the text, as before.

InsertHyperlinkAction now snapshots the affected paragraphs and resolves
its range through paragraph anchors, like DeleteRangeAction, so undo
restores the replaced text with its original formatting and redo after
undo no longer throws on stale inline indices. HyperlinkEngine gains an
InsertHyperlink overload taking displayText. The MAUI prompt only asks
for a URL, so it is unchanged.

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 16:40

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

Unresolved insertion-boundary, snapshot-preservation, image-undo, and layout-cache issues can cause incorrect placement, content loss, or stale rendering.

4 open issues
What changed in this PR

Makes hyperlink insertion honor DisplayText and updates undo/redo to restore selected text and formatting.

Changes:

  • Adds display-text support while preserving existing overloads.
  • Uses paragraph snapshots and character-offset anchors for undo/redo.
  • Adds command tests and updates documentation.
File Description
tests/​MintedTextEditor.Core.Tests/​CommandTests.cs Tests display text, selection replacement, and undo.
src/​MintedTextEditor.Core/​Formatting/​HyperlinkEngine.cs Adds a display-text overload.
src/​MintedTextEditor.Core/​Editing/​UndoableActions.cs Adds snapshots and anchors for hyperlink undo/redo.
src/​MintedTextEditor.Core/​Commands/​HyperlinkCommands.cs Passes DisplayText to the undoable action.
docs/​images-hyperlinks.md Documents display-text behavior and overloads.
CHANGELOG.md Records the hyperlink fixes.

Review effort: Balanced

- Paragraph snapshots now hold the original paragraph and inline objects
  plus their state at capture time, and restore those same objects
  (recursively for link children) instead of building copies. An earlier
  InsertImageAction still finds its image after a later link insertion is
  undone, so undoing it removes the image and redo no longer duplicates it.
  LineBreak no longer comes back as text, and links keep non-text children.
- Undo that reinserts paragraphs merged away by a multi-paragraph
  selection (InsertHyperlinkAction, DeleteRangeAction) reports BlockSplit,
  so LayoutCache drops every later block's cached layout. Both use one
  shared SnapshotHelper.RestoreBlocks, which only reinserts paragraphs no
  longer in the document; ApplyStyleAction uses it too, so each snapshot
  restores its own paragraph even with a table inside the range.
- HyperlinkEngine inserts a link after an image or existing link when the
  caret is at that inline's end (where undo anchors resolve a caret at the
  start of the following text), instead of before it, and sets Parent on
  the link's children and on the split-off run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197tHRWhTiagB1MTsDuPkoa
michaelstonis pushed a commit that referenced this pull request Oct 21, 2026
…napshots

SnapshotHelper rebuilt every inline from copied data on restore, so undo steps
recorded earlier that hold the original objects (InsertHyperlinkAction,
InsertImageAction) could no longer find them: insert a link, remove or format
it, undo twice, and the link stayed. Snapshots also turned LineBreak into a
"\n" text run and kept only TextRun children of hyperlinks.

Adopt the identity-preserving SnapshotHelper from PR #28
(housekeeping/hyperlink-display-text) verbatim so the two PRs merge cleanly:
snapshots keep each original inline (and paragraph) with its state, restore
puts the same objects back, line breaks and other inline types are kept as
themselves, and hyperlink children are captured recursively. The inline-only
restore used by ApplyStyleAction and RemoveHyperlinkAction is now a small
ApplyStyleAction.RestoreInlines wrapper outside SnapshotHelper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197tHRWhTiagB1MTsDuPkoa
@michaelstonis
michaelstonis merged commit 2de48c2 into main Oct 21, 2026
michaelstonis added a commit that referenced this pull request Oct 21, 2026
* fix: record character formatting and hyperlink removal for undo

Bold/italic/underline/strikethrough/sub/superscript/clear-formatting commands,
Ctrl+B/I/U, the font family/size dropdowns and the text/highlight colour
pickers called FormattingEngine/FontFormattingEngine directly, so none of them
could be undone. ApplyStyleAction existed but only took a fixed transform (the
toggles decide on/off from the current runs), restored the paragraph style on
undo, and paired snapshots with block indices by position, restoring the wrong
paragraph when a table sat inside the range.

ApplyStyleAction now also wraps any formatting operation, snapshots each
paragraph with its block index, restores only the inlines on undo, and merges
split runs after applying. EditorContext.ApplyCharacterFormatting pushes one
ApplyStyleAction for a non-empty selection (keeping the selection on the same
text) and leaves a collapsed selection to the engines' pending style, which is
not recorded. The commands, keyboard shortcuts and MAUI dropdowns/pickers all
go through it.

HyperlinkEngine.RemoveHyperlink now keeps the link's runs and their styles
instead of one plain run, and RemoveHyperlinkCommand pushes a new
RemoveHyperlinkAction that snapshots the paragraph so undo restores the link.

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

* fix: address review — keep inline identity and lose nothing in undo snapshots

SnapshotHelper rebuilt every inline from copied data on restore, so undo steps
recorded earlier that hold the original objects (InsertHyperlinkAction,
InsertImageAction) could no longer find them: insert a link, remove or format
it, undo twice, and the link stayed. Snapshots also turned LineBreak into a
"\n" text run and kept only TextRun children of hyperlinks.

Adopt the identity-preserving SnapshotHelper from PR #28
(housekeeping/hyperlink-display-text) verbatim so the two PRs merge cleanly:
snapshots keep each original inline (and paragraph) with its state, restore
puts the same objects back, line breaks and other inline types are kept as
themselves, and hyperlink children are captured recursively. The inline-only
restore used by ApplyStyleAction and RemoveHyperlinkAction is now a small
ApplyStyleAction.RestoreInlines wrapper outside SnapshotHelper.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
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