Skip to content

fix(markdown): remount CodeMirror code blocks after IR-mode content updates - #612

Closed
ONEGAYI wants to merge 1 commit into
cweijan:mainfrom
ONEGAYI:fix/preview-code-block-scroll
Closed

ONEGAYI wants to merge 1 commit into
cweijan:mainfrom
ONEGAYI:fix/preview-code-block-scroll

Conversation

@ONEGAYI

@ONEGAYI ONEGAYI commented Sep 16, 2026

Copy link
Copy Markdown

Problem

1. IR mode: code blocks lose their CodeMirror editors after external content updates

When the document is replaced wholesale in IR mode — i.e. after an external file change (the extension host posts update, the webview calls editor.setValue()) or an AI selection replace (applyAIResult) — every code block degrades to plain rendered pre > code: no syntax highlighting, no CodeMirror editing. Clicking the block once re-activates it.

2. Stock vditor inline max-height cap on rendered code

codeRender still sets e.style.maxHeight = (window.outerHeight - 40) + "px" on every pre > code it processes. Inside a webview window.outerHeight is unreliable (it can even be 0), and any code block taller than the cap gets a nested in-block scrollbar on top of the document scroll. Code-block height is already governed in this fork by the --cm-block-max-height setting (with its expand/collapse affordance), so the inline cap is both buggy and inconsistent with that system.

Root cause

For problem 1: both IR full-replace paths only re-run the Lute DOM rewrite plus the .vditor-ir__preview[data-render='2'] → processCodeRender pass. That pass never mounts CodeMirror on regular code blocks — it short-circuits them to ensureSpecialCodeBlockPreview, which only handles math/mermaid/plantuml blocks. The CodeMirror mount entry point renderCodeBlocks(vditor) was simply never called on these paths. The wysiwyg branch does not have the bug because renderDomByMd calls renderCodeBlocks after the rewrite.

For problem 2: stock vditor behavior carried over in codeRender.ts.

Fix

  • vditor/src/index.ts: call renderCodeBlocks(vditor) (+ setupLazyCodeMirrorObserver) after the DOM rewrite in setValue's IR branch, and make the applyAIResult renderCodeBlocks call unconditional instead of wysiwyg-only — matching renderDomByMd and the edit-mode switch path. Lazy mounting keeps the cost bounded: only blocks near the viewport get a CodeMirror instance, off-screen blocks render as placeholders and mount on scroll.
  • vditor/src/ts/markdown/codeRender.ts: remove the inline max-height assignment; the idempotency guard now detects the inserted copy button (.vditor-copy previous sibling) instead of the maxHeight side effect — which also stays correct when the processed DOM is cloned.

Verification

Built the vditor subpackage (npm run build) and drove the built bundle in a real browser against a wysiwyg and an IR editor instance:

  • IR + setValue (external update): before the fix cmHosts is empty and the bare pre > code stays plain; clicking the block mounts CodeMirror. After the fix, CodeMirror stays mounted at the initial/immediate/settled checkpoints; getValue() round-trips.
  • wysiwyg: no behavior change (mount state and value round-trip verified before/after setValue).
  • codeRender: called twice on the same block — exactly one copy button inserted, no inline max-height.
  • Main extension build passes; no maxHeight consumer is affected (the chrome overflow check reads .cm-scroller's computed style, unrelated to this inline style).

The AI replace path fix is the same pattern as the verified setValue path (that specific panel flow was not driven end-to-end in the browser).

…pdates

In IR mode, both full-document replacement paths (setValue, used by the
extension host after external file changes, and applyAIResult's partial
AI replace) only re-ran the Lute DOM rewrite plus the data-render='2'
preview pass. Neither mounts CodeMirror on regular code blocks:
processCodeRender short-circuits them to ensureSpecialCodeBlockPreview,
which only handles math/mermaid/plantuml. After any external update
every code block degraded to plain rendered pre/code until the user
clicked it once to re-activate the editor.

- Call renderCodeBlocks after the rewrite in both IR paths, matching
  the wysiwyg branch (renderDomByMd) and the edit-mode switch path.
  Lazy mounting keeps the cost bounded: only blocks near the viewport
  get a CodeMirror instance, others render as placeholders.
- Drop the stock vditor inline max-height cap (window.outerHeight - 40)
  from codeRender. Inside the webview outerHeight is unreliable (it can
  be 0) and code-block height is already governed by the
  --cm-block-max-height setting. The idempotency check now detects the
  inserted copy button instead of the maxHeight side effect, which also
  stays correct when DOM is cloned.
@ONEGAYI

ONEGAYI commented Oct 7, 2026

Copy link
Copy Markdown
Author

Closing due to inactivity — no review feedback after ~3 weeks. The commits remain available via refs/pull/612/head in this PR if anyone wants to revisit. Thanks!

@ONEGAYI ONEGAYI closed this Oct 7, 2026
@ONEGAYI
ONEGAYI deleted the fix/preview-code-block-scroll branch October 7, 2026 03:52
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.

1 participant