Skip to content

[lexical-code-core][lexical-code-prism][lexical-code-shiki] Bug Fix: code blocks keep the caret and their own attributes - #9056

Open
LeSingh1 wants to merge 1 commit into
facebook:mainfrom
LeSingh1:consol/code-block-editing
Open

[lexical-code-core][lexical-code-prism][lexical-code-shiki] Bug Fix: code blocks keep the caret and their own attributes#9056
LeSingh1 wants to merge 1 commit into
facebook:mainfrom
LeSingh1:consol/code-block-editing

Conversation

@LeSingh1

Copy link
Copy Markdown
Contributor

Description

Code blocks mishandle their own line and selection bookkeeping: the highlighter
restores a selection point that the re-highlight has already invalidated, the
line-shift and outdent helpers mis-measure a line that is blank or whose caret
sits at column 0, and the importer never reads back the data-theme that the
exporter writes. The user-visible result is a caret that gets dragged into (or
thrown out of) a code block whenever it re-highlights or re-indents, and a block
that loses its own attributes on an HTML round trip.

This consolidates five fixes:

Test plan

New unit tests per fix: selection-offset retention for both Prism and Shiki,
caret placement after a markdown import that ends in a code block, Alt+Arrow
across a blank line, data-theme import plus an exportDOM/importDOM round
trip, and Shift+Tab at column 0 (tab indent, space indent, a non-zero column,
and an unindented no-op control).

Before

Source fixes reverted, new tests kept:

$ npx vitest run --project unit packages/lexical-code-core packages/lexical-code-prism packages/lexical-code-shiki

 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodeHighlighterPrismRetainSelection.test.ts > CodeHighlighterPrism $updateAndRetainSelection > retains a non-negative offset for code content "\nfoo" (#8943)
 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodeHighlighterPrismRetainSelection.test.ts > CodeHighlighterPrism $updateAndRetainSelection > retains a non-negative offset for code content "\n\nfoo" (#8943)
 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodePrismRetainSelection.test.ts > Prism highlighting only retains a selection it owns (#6305) > importing markdown that ends in a code block leaves the caret at the document start
 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodePrismRetainSelection.test.ts > Prism highlighting only retains a selection it owns (#6305) > the caret is not dragged into the last of several imported code blocks
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > CodeHighlighterShiki $updateAndRetainSelection > retains a non-negative offset for code content "\nfoo" (#8943)
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > CodeHighlighterShiki $updateAndRetainSelection > retains a non-negative offset for code content "\n\nfoo" (#8943)
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > Shiki highlighting only retains a selection it owns (#6305) > importing markdown that ends in a code block leaves the caret at the document start
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > Shiki highlighting only retains a selection it owns (#6305) > the caret is not dragged into the last of several imported code blocks
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts > CodeImportExtension > <pre data-theme="poimandres"> restores the theme
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts > CodeImportExtension > multi-line <code data-theme> restores the theme
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeIndentation.test.ts > CodeIndentExtension > shiftLines > moves a line up past a blank line without merging it into the line above
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeIndentation.test.ts > CodeIndentExtension > shiftLines > moves a line down past a blank line without merging it into the line below
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeNode.test.ts > CodeNode > round-trips the theme through exportDOM/importDOM
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts > OUTDENT_CONTENT_COMMAND at the start of a code line > outdents when the caret is collapsed at column 0 (on the TabNode)
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts > OUTDENT_CONTENT_COMMAND at the start of a code line > outdents when the caret is collapsed at column 0 of the code text
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts > OUTDENT_CONTENT_COMMAND at the start of a code line > strips a space indent from column 0 when tabSize is configured

 Test Files  7 failed | 7 passed (14)
      Tests  16 failed | 218 passed (234)

After

$ npx vitest run --project unit packages/lexical-code-core packages/lexical-code-prism packages/lexical-code-shiki

 Test Files  14 passed (14)
      Tests  234 passed (234)

$ npx tsc --noEmit -p .
(clean)

Supersedes #8947, #8972, #8986, #8988, #8992, consolidated per the review
feedback on #9027 and #9035.

…code blocks keep the caret and their own attributes

## Description

Code blocks mishandle their own line and selection bookkeeping: the highlighter
restores a selection point that the re-highlight has already invalidated, the
line-shift and outdent helpers mis-measure a line that is blank or whose caret
sits at column 0, and the importer never reads back the `data-theme` that the
exporter writes. The user-visible result is a caret that gets dragged into (or
thrown out of) a code block whenever it re-highlights or re-indents, and a block
that loses its own attributes on an HTML round trip.

This consolidates five fixes:

- **facebook#8947** — `$updateAndRetainSelection` in `CodeHighlighterPrism.ts` and
  `CodeHighlighterShiki.ts` walked the code node's children with `.some()` and
  only decremented the running offset for text nodes, so a `LineBreakNode` at
  the start of a code line drove `textOffset` negative and the next text node
  was selected at an out-of-range offset. The walk is now an explicit loop: a
  `LineBreakNode` consumes one unit of offset, and when the offset lands on one
  an element point on the code node is used instead of a negative text point.

- **facebook#8972** — the same `$updateAndRetainSelection` retained the selection even
  when the anchor was not inside the code node being highlighted, dragging the
  caret into a code block after e.g. a markdown import. It now falls back to a
  plain `updateFn()` unless the anchor is the code node or a descendant of it.

- **facebook#8986** — `$handleShiftLines` in `CodeIndentation.ts` accepted a
  `LineBreakNode` sibling as an insertion-point candidate, but that linebreak
  belongs to a *different* line, so Alt+Arrow across a blank line spliced the
  moving line into the line beyond it and merged the two. A blank adjacent line
  is now detected up front and the range is re-inserted directly after the
  sibling linebreak, in both directions.

- **facebook#8988** — `$convertPreElement` in `CodeNode.ts` and the `pre` /
  multiline-`code` import rules in `CodeImportExtension.ts` read `data-language`
  but not `data-theme`, so `exportDOM` wrote a theme that `importDOM` silently
  dropped. Both now pass `data-theme` through to `$createCodeNode`.

- **facebook#8992** — `$getCodeLines` drops a trailing line when the selection ends
  exactly at its start, which for a collapsed caret is always true, so Shift+Tab
  at column 0 never reached the outdent loop and silently did nothing. A new
  `$outdentLineAtCaret` in `CodeIndentation.ts` resolves the line from the
  anchor and applies the same rule as that loop: strip a leading `TabNode`, or
  `tabSize` leading spaces when the extension is configured for them.

## Test plan

New unit tests per fix: selection-offset retention for both Prism and Shiki,
caret placement after a markdown import that ends in a code block, Alt+Arrow
across a blank line, `data-theme` import plus an `exportDOM`/`importDOM` round
trip, and Shift+Tab at column 0 (tab indent, space indent, a non-zero column,
and an unindented no-op control).

### Before

Source fixes reverted, new tests kept:

```
$ npx vitest run --project unit packages/lexical-code-core packages/lexical-code-prism packages/lexical-code-shiki

 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodeHighlighterPrismRetainSelection.test.ts > CodeHighlighterPrism $updateAndRetainSelection > retains a non-negative offset for code content "\nfoo" (facebook#8943)
 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodeHighlighterPrismRetainSelection.test.ts > CodeHighlighterPrism $updateAndRetainSelection > retains a non-negative offset for code content "\n\nfoo" (facebook#8943)
 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodePrismRetainSelection.test.ts > Prism highlighting only retains a selection it owns (facebook#6305) > importing markdown that ends in a code block leaves the caret at the document start
 FAIL  |unit| packages/lexical-code-prism/src/__tests__/unit/CodePrismRetainSelection.test.ts > Prism highlighting only retains a selection it owns (facebook#6305) > the caret is not dragged into the last of several imported code blocks
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > CodeHighlighterShiki $updateAndRetainSelection > retains a non-negative offset for code content "\nfoo" (facebook#8943)
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > CodeHighlighterShiki $updateAndRetainSelection > retains a non-negative offset for code content "\n\nfoo" (facebook#8943)
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > Shiki highlighting only retains a selection it owns (facebook#6305) > importing markdown that ends in a code block leaves the caret at the document start
 FAIL  |unit| packages/lexical-code-shiki/src/__tests__/unit/CodeShikiRetainSelection.test.ts > Shiki highlighting only retains a selection it owns (facebook#6305) > the caret is not dragged into the last of several imported code blocks
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts > CodeImportExtension > <pre data-theme="poimandres"> restores the theme
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeImportExtension.test.ts > CodeImportExtension > multi-line <code data-theme> restores the theme
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeIndentation.test.ts > CodeIndentExtension > shiftLines > moves a line up past a blank line without merging it into the line above
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeIndentation.test.ts > CodeIndentExtension > shiftLines > moves a line down past a blank line without merging it into the line below
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeNode.test.ts > CodeNode > round-trips the theme through exportDOM/importDOM
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts > OUTDENT_CONTENT_COMMAND at the start of a code line > outdents when the caret is collapsed at column 0 (on the TabNode)
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts > OUTDENT_CONTENT_COMMAND at the start of a code line > outdents when the caret is collapsed at column 0 of the code text
 FAIL  |unit| packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts > OUTDENT_CONTENT_COMMAND at the start of a code line > strips a space indent from column 0 when tabSize is configured

 Test Files  7 failed | 7 passed (14)
      Tests  16 failed | 218 passed (234)
```

### After

```
$ npx vitest run --project unit packages/lexical-code-core packages/lexical-code-prism packages/lexical-code-shiki

 Test Files  14 passed (14)
      Tests  234 passed (234)

$ npx tsc --noEmit -p .
(clean)
```

Supersedes facebook#8947, facebook#8972, facebook#8986, facebook#8988, facebook#8992, consolidated per the review
feedback on facebook#9027 and facebook#9035.
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

@LeSingh1 is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant