Skip to content

fix(preset): anchor mark input rules to the cursor to prevent paste corruption - #2433

Merged
Saul-Mirone merged 1 commit into
mainfrom
fix/2400-paste-special-char
Jul 29, 2026
Merged

Saul-Mirone merged 1 commit into
mainfrom
fix/2400-paste-special-char

Conversation

@Saul-Mirone

Copy link
Copy Markdown
Member
  • I read the contributing guide
  • I agree to follow the code of conduct

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 with start = 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 customInputRules runs the rules from handleKeyDown with text = '\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 the m flag) 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-commonmark and preset-gfm (__test__/paste-special-char.spec.ts) that:

  • reproduce the bug: two pastes ending in _ / ~, then Space and then Enter — asserting the document text is preserved and no emphasis / strike_through mark leaks in. These fail on main (a character after the first paste is dropped, e.g. ...()_This......()_his...) and pass with this change.
  • guard the happy path: typing _on the grass_ / ~~on the grass~~ still creates the mark, and intra-word the_lunatic_is / C:/the/~lunatic~ still do not (mirrors the existing input-rule e2e tests).
Test Files  10 passed (10)
     Tests  64 passed (64)

oxlint and oxfmt are clean.

Copilot AI review requested due to automatic review settings July 29, 2026 13:08
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
milkdown-storybook Ready Ready Preview, Comment Jul 29, 2026 1:13pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 3d43982

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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-commonmark and preset-gfm to 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
@Saul-Mirone
Saul-Mirone force-pushed the fix/2400-paste-special-char branch from 42ec9bb to 3d43982 Compare July 29, 2026 13:13
Copilot AI review requested due to automatic review settings July 29, 2026 13:13
@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@milkdown/components

npm i https://pkg.pr.new/@milkdown/components@2433

@milkdown/core

npm i https://pkg.pr.new/@milkdown/core@2433

@milkdown/crepe

npm i https://pkg.pr.new/@milkdown/crepe@2433

@milkdown/ctx

npm i https://pkg.pr.new/@milkdown/ctx@2433

@milkdown/exception

npm i https://pkg.pr.new/@milkdown/exception@2433

@milkdown/kit

npm i https://pkg.pr.new/@milkdown/kit@2433

@milkdown/prose

npm i https://pkg.pr.new/@milkdown/prose@2433

@milkdown/transformer

npm i https://pkg.pr.new/@milkdown/transformer@2433

@milkdown/utils

npm i https://pkg.pr.new/@milkdown/utils@2433

@milkdown/react

npm i https://pkg.pr.new/@milkdown/react@2433

@milkdown/vue

npm i https://pkg.pr.new/@milkdown/vue@2433

@milkdown/plugin-automd

npm i https://pkg.pr.new/@milkdown/plugin-automd@2433

@milkdown/plugin-block

npm i https://pkg.pr.new/@milkdown/plugin-block@2433

@milkdown/plugin-clipboard

npm i https://pkg.pr.new/@milkdown/plugin-clipboard@2433

@milkdown/plugin-collab

npm i https://pkg.pr.new/@milkdown/plugin-collab@2433

@milkdown/plugin-cursor

npm i https://pkg.pr.new/@milkdown/plugin-cursor@2433

@milkdown/plugin-diff

npm i https://pkg.pr.new/@milkdown/plugin-diff@2433

@milkdown/plugin-emoji

npm i https://pkg.pr.new/@milkdown/plugin-emoji@2433

@milkdown/plugin-highlight

npm i https://pkg.pr.new/@milkdown/plugin-highlight@2433

@milkdown/plugin-history

npm i https://pkg.pr.new/@milkdown/plugin-history@2433

@milkdown/plugin-indent

npm i https://pkg.pr.new/@milkdown/plugin-indent@2433

@milkdown/plugin-listener

npm i https://pkg.pr.new/@milkdown/plugin-listener@2433

@milkdown/plugin-prism

npm i https://pkg.pr.new/@milkdown/plugin-prism@2433

@milkdown/plugin-slash

npm i https://pkg.pr.new/@milkdown/plugin-slash@2433

@milkdown/plugin-streaming

npm i https://pkg.pr.new/@milkdown/plugin-streaming@2433

@milkdown/plugin-tooltip

npm i https://pkg.pr.new/@milkdown/plugin-tooltip@2433

@milkdown/plugin-trailing

npm i https://pkg.pr.new/@milkdown/plugin-trailing@2433

@milkdown/plugin-upload

npm i https://pkg.pr.new/@milkdown/plugin-upload@2433

@milkdown/preset-commonmark

npm i https://pkg.pr.new/@milkdown/preset-commonmark@2433

@milkdown/preset-gfm

npm i https://pkg.pr.new/@milkdown/preset-gfm@2433

@milkdown/theme-nord

npm i https://pkg.pr.new/@milkdown/theme-nord@2433

commit: 3d43982

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread packages/plugins/preset-commonmark/src/mark/emphasis.ts
Comment thread packages/plugins/preset-gfm/src/mark/strike-through.ts
@Saul-Mirone
Saul-Mirone added this pull request to the merge queue Jul 29, 2026
Merged via the queue into main with commit 4cf2431 Jul 29, 2026
18 checks passed
@Saul-Mirone
Saul-Mirone deleted the fix/2400-paste-special-char branch July 29, 2026 16:13
@github-actions github-actions Bot mentioned this pull request Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] | Pasting With Special Character | Edge Case

2 participants