Skip to content

Text inside bold, italic, or a link label is still over-escaped on save #271

Description

@Azganoth

Summary

#252 made the serializer escape a character only where it can still be syntax where it sits, but the fix stops at a mark boundary. Text inside bold, italic, strikethrough, or a link label is still escaped by the blanket rule, so the same over-escaping #252 removed from ordinary text survives everywhere a mark wraps it.

It matters beyond the extra backslashes. #255 derives what it shows from what the serializer writes, so any run that keeps over-escaping is a run that gesture cannot honestly cover, and it is what keeps #255 scoped to plain text.

Steps to reproduce

  1. Open a document containing **\[a](b)**.
  2. Save it.
  3. Read the saved file.

Expected behavior

**\[a](b)**, with the one escape the run needs, matching what #252 already gives the same text outside a mark.

Actual behavior

**\[a]\(b)**. The \( returns, because the \[ alone is enough to keep the run literal and only the relaxed rule knows that.

Measured against 621866a8, with the unmarked control alongside:

Document text Saved
**[a](b)** **\[a]\(b)**
*text [a](b) more* *text \[a]\(b) more*
**a [x](y) b** **a \[x]\(y) b**
See [a](b) here. (control, unmarked) See \[a](b) here.

Related context

Done when

  • Text inside strong, emphasis, delete, and a link label is escaped with the same precision as text directly in a paragraph, heading, or table cell.
  • **\[a](b)** saves as **\[a](b)**, and the three measured cases above keep one escape each.
  • Regression coverage over the marked forms beside the existing unmarked cases.
  • CHANGELOG.md records the narrowed escaping.

Notes

Diagnosis

readPhrasingNeighbors returns undefined unless the mdast parent type is in WHOLE_LINE_PHRASING_PARENTS, which holds heading, paragraph, and tableCell. relaxBracketEscapes returns immediately on an undefined neighbours value, so neither of its passes runs for text whose parent is strong, emphasis, delete, or link. relaxAttentionEscapes still runs, since neighbours are optional there, but without textOnly and the sibling text it loses the pairing information that decides whether an attention run can be relaxed.

The document survives the round trip either way — **\[a]\(b)** reopens as the same document — so this is over-escaping in the class #252 fixed, not a semantic loss.

Implementation direction

Adding the mark types to WHOLE_LINE_PHRASING_PARENTS is unlikely to be enough on its own. The context those passes need is the whole line: earlier, later, and laterHasMarkup describe the siblings across the line, and inside a mark the immediate parent holds only the marked fragment. The nearest whole-line ancestor and the node's position within its flattened phrasing sequence are what the passes want, and state.indexStack already carries the full path to derive them.

A link label is not simply another mark for this purpose. Inside a label a ] genuinely closes the label wherever it sits, so its relaxation rules differ from those of a run inside strong, and it is worth confirming separately rather than folding in.

Out of scope

Metadata

Metadata

Assignees

Labels

BugSomething isn't working

Projects

Status
Ready

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions