fix(editor): code block Backspace/Delete after rich paste (#2196)#2199
Open
Toseef-Ahmad wants to merge 1 commit into
Open
fix(editor): code block Backspace/Delete after rich paste (#2196)#2199Toseef-Ahmad wants to merge 1 commit into
Toseef-Ahmad wants to merge 1 commit into
Conversation
) Rich clipboard / AI paste into code blocks broke character deletion when DOM (Prism + <br>) and model offsets diverged. - Strip zero-width/format chars in code setValue/getTextValue (U.String) - Editable: plainDomTextOffsets for code; focus.apply skips ZWS mapping on .textCode; onFocus skips domToModel for code - Tighten Backspace range checks; onBackspaceBlock uses explicit caret-at-0 - Code blocks: handle Backspace/Delete programmatically on the model string (UTF-16 surrogate aware) with preventDefault to bypass unreliable native contenteditable deletion Co-authored-by: Cursor <cursoragent@cursor.com>
|
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
Contributor
|
I will look at it more precisely next week, have a lot of stuff going in right now. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You can paste this into the PR description on GitHub:
Summary
Fixes #2196 — after pasting content from rich sources (e.g. AI “Copy response”, Markdown-heavy snippets) into a code block, Backspace and Delete often did not remove characters, or editing felt “stuck” even though new typing still worked.
What was going wrong
Code blocks render as a contenteditable whose HTML is built from plain text (syntax highlighting with Prism, newlines as
). That stack is different from normal paragraph text with marks.
Several things could line up badly:
Invisible Unicode (zero-width spaces, BOM, word joiner, etc.) from the clipboard could end up in the stored text. That made Mark.hasZws() treat the field like mark-anchored rich text and turned on ZWS-aware DOM ↔ model range conversion — which is meant for mentions/emoji, not for source code. Caret and offsets could disagree with what you see.
Backspace handling used if (range.to) as a proxy for “not at the start of the block”. In JavaScript, 0 is falsy, so a collapsed caret at offset 0 was mixed with broken or missing range.to. That could run the wrong branch, re-save stale text, set ret = true, and skip the rest of the key handler — fighting the browser’s native delete.
Even after tightening ranges and ZWS behavior, native contenteditable deletion inside long pasted
/ Prism trees could still disagree with the plain-text model the app persists. The reliable fix for code is to not depend on the browser for single-character Backspace/Delete there.
What we changed
Added U.String.stripZeroWidthFormatChars() and call it when reading/writing code block text so invisible format characters do not accumulate in the model.
Editable: new prop plainDomTextOffsets — for code blocks, selection offsets match selection-ranges directly (no Mark ZWS dom/model conversion).
focus.apply: if the focused node is inside .textCode, skip modelToDom ZWS conversion.
BlockText onFocus: skip domToModel for code when reading the selection from the DOM.
Replaced fragile if (range.to) / !range.to logic with explicit checks: non-empty caret or selection vs collapsed at offset 0.
onBackspaceBlock: merge-at-start-of-block now requires range.from === range.to === 0 instead of !range.to, so missing to does not look like “start of block”.
For code blocks only (menus closed): on Backspace / Delete, preventDefault and update the block using the current getTextValue() string:
Selection: remove the selected range.
Collapsed caret: remove one UTF-16 code unit or a full surrogate pair (emoji) before/after the caret via U.String.utf16DeleteLengthBefore / utf16DeleteLengthAfter.
Then blockSetText + setValue so the DOM and store stay aligned.
Caret at offset 0 is not intercepted here so merge with the previous block still works as before.
Scope / risk
Touched files (5): string.ts, block/text.tsx, form/editable.tsx, lib/focus.ts, editor/page.tsx.
Behavior change is scoped to code-style text blocks and Backspace/Delete (plus the small, targeted range and focus fixes above).
Non-code paragraphs, titles, lists, etc. keep their existing paths; mark-related Backspace logic stays in the non-code branch.
How to verify (manual)
Copy a long snippet from an AI assistant or any source that puts Markdown + emoji + fenced blocks in the clipboard.
Paste into a code block in the editor.
Place the caret inside the pasted text and use Backspace and Delete — each key should remove one visible character (or the selection), including around emoji.
Move the caret to the very start of the block and press Backspace — block merge / previous-block focus should still behave as today.
Smoke-test a normal paragraph (not code): Backspace, Delete, marks, and merge-at-start unchanged.
Checklist
Fixes #2196 (code block editing after rich paste)
Single logical change-set; no unrelated refactors
Typecheck / lint clean on changed files
Short title for the PR:
fix(editor): code block Backspace/Delete after rich paste (#2196)
I have read the CLA Document and I hereby sign the CLA