fix(preset): anchor mark input rules to the cursor to prevent paste corruption - #2433
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
There was a problem hiding this comment.
Pull request overview
This PR fixes a ProseMirror input-rule range-calculation bug that could corrupt the document when users paste text ending with _ or ~ twice, then press Space or Enter, by ensuring the relevant mark input-rule regexes only match at the cursor.
Changes:
- Anchors the underscore-emphasis input rule (CommonMark) to the cursor with
$to prevent cross-paste matches. - Anchors the strikethrough input rule (GFM) to the cursor with
$for the same class of corruption. - Adds regression + “happy path” typing tests in both
preset-commonmarkandpreset-gfmto cover Space and Enter triggers.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/plugins/preset-gfm/src/mark/strike-through.ts | Anchors the strikethrough input-rule regex to the cursor ($) to prevent cross-paste matching. |
| packages/plugins/preset-gfm/src/test/paste-special-char.spec.ts | Adds regression tests reproducing the paste-then-Space/Enter corruption for ~, plus typing behavior guards. |
| packages/plugins/preset-commonmark/src/mark/emphasis.ts | Anchors the underscore emphasis input-rule regex to the cursor ($) to prevent cross-paste matching. |
| packages/plugins/preset-commonmark/src/test/paste-special-char.spec.ts | Adds regression tests reproducing the paste-then-Space/Enter corruption for _, plus typing behavior guards. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…orruption The underscore-emphasis and strikethrough input-rule regexes were not anchored to the end of the input (`$`), unlike the star-emphasis, strong and inline-code rules. When text ending with `_` (or `~`) was pasted twice and the user then pressed Space or Enter, the regex matched a `_..._` / `~...~` span *across* the two pastes — a match that did not end at the cursor. prosemirror's input-rule runner assumes the match ends at the cursor when it computes the affected range, so the off-by-one range deleted characters after the first paste. Adding the `$` anchor makes the rules fire only when the closing marker is typed at the cursor, which is the intended behavior and matches the other mark rules. Typing `_emphasis_` / `~~strike~~` by hand is unaffected. Closes #2400
42ec9bb to
3d43982
Compare
@milkdown/components
@milkdown/core
@milkdown/crepe
@milkdown/ctx
@milkdown/exception
@milkdown/kit
@milkdown/prose
@milkdown/transformer
@milkdown/utils
@milkdown/react
@milkdown/vue
@milkdown/plugin-automd
@milkdown/plugin-block
@milkdown/plugin-clipboard
@milkdown/plugin-collab
@milkdown/plugin-cursor
@milkdown/plugin-diff
@milkdown/plugin-emoji
@milkdown/plugin-highlight
@milkdown/plugin-history
@milkdown/plugin-indent
@milkdown/plugin-listener
@milkdown/plugin-prism
@milkdown/plugin-slash
@milkdown/plugin-streaming
@milkdown/plugin-tooltip
@milkdown/plugin-trailing
@milkdown/plugin-upload
@milkdown/preset-commonmark
@milkdown/preset-gfm
@milkdown/theme-nord
commit: |
Summary
Fixes #2400.
Pasting text that ends with
_(e.g.This is for qa34%^%^&&&(&(&(&()(*()_) twice and then pressing Space or Enter deletes characters after the first paste.Root cause
The underscore-emphasis and strikethrough input-rule regexes are not anchored to the end of the input (
$), unlike the star-emphasis, strong and inline-code rules:emphasisUnderscoreInputRule:/\b_(?![_\s])(.*?[^_\s])_\b/strikethroughInputRule:/(?<![\w:/])(~{1,2})(.+?)\1(?!\w|\/)/After two pastes the document contains
..._..._. When Space/Enter runs the input rules,regexp.exec(textBefore)matches an_..._(or~...~) span across the two pastes — a match that does not end at the cursor. ProseMirror's input-rule runner computes the affected range withstart = from - (match[0].length - text.length), assuming the match ends at the cursor. Because it doesn't, the range is off by one and characters are deleted.Enter triggers this too because Milkdown's
customInputRulesruns the rules fromhandleKeyDownwithtext = '\n'.Fix
Anchor both rules to the cursor with
$. The rules now fire only when the closing marker is typed at the cursor — the intended behavior, and consistent with the other mark rules.$(without themflag) does not match before a trailing\n, so the Enter case is fixed as well. Typing_emphasis_/~~strike~~by hand is unchanged (the closing marker is at the cursor, so$matches).How did you test this change?
Added regression tests in
preset-commonmarkandpreset-gfm(__test__/paste-special-char.spec.ts) that:_/~, then Space and then Enter — asserting the document text is preserved and noemphasis/strike_throughmark leaks in. These fail onmain(a character after the first paste is dropped, e.g....()_This...→...()_his...) and pass with this change._on the grass_/~~on the grass~~still creates the mark, and intra-wordthe_lunatic_is/C:/the/~lunatic~still do not (mirrors the existing input-rule e2e tests).oxlintandoxfmtare clean.