Skip to content

perf(preview): let render_markdown split its output into top-level blocks - #1023

Merged
PathGao merged 1 commit into
masterfrom
perf/split-blocks-rust
Oct 24, 2026
Merged

PathGao merged 1 commit into
masterfrom
perf/split-blocks-rust

Conversation

@PathGao

@PathGao PathGao commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

In split view, every typing pause re-renders the whole document on the main thread: about 0.4 s per pause at 1 MB and 2.3 s at 5 MB, to change one paragraph. This is the first of three PRs that make a pause cost the size of the edit instead. This one only touches the renderer, and the frontend does not use it yet.

render_markdown takes an optional splitBlocks. When it is true, the renderer writes \0 on a line of its own between the top-level blocks of the document. comrak still parses the whole document, so footnote numbers, reference links and heading-id dedupe are already in each block's markup. The next PR diffs those blocks and processes and sanitizes only the ones that changed. StackEdit does the same thing: it renders the whole token stream with a marker at each section boundary, splits on it, and only sanitizes and inserts the sections that changed (markdownConversionSvc.js, editorSvc.js). VS Code (morphdom over a full render) and HedgeDoc 1 (an outerHTML compare of top-level elements) also re-parse the whole document and only touch what changed in the DOM. Neither one skips the full sanitize the way this does.

Three blocks are kept whole:

  • Footnote definitions. comrak opens one <section class="footnotes"> on the first definition and closes it after the last.
  • Raw HTML left open across blocks, such as <details>, then Markdown, then </details>. That is three top-level blocks to comrak and one element to the browser. The formatter counts the tags each raw HTML block opens and closes (void and self-closing tags excluded) and writes no separator while any are open. If an author's HTML never closes, the rest of the document becomes one block. That is slower but still correct.
  • The separator itself. No document can write it: comrak turns NUL into U+FFFD, and the math restore now does the same.

Without the flag the output is byte-for-byte what it was. That covers the export, which does not pass it. The tests pin this on all five UTF-8 samples. PreviewFormatter now carries a usize, the open-tag count the split formatter keeps. convert_markdown is test-only now, and the source tests that read its body point at convert_markdown_with.

Verification: cargo fmt --check, cargo clippy --all-targets -D warnings and cargo test (224 tests) pass. So do npm run check, npm test and vitest. New tests cover one separator per top-level block, footnotes as one block, <details> around Markdown, unclosed raw HTML, void tags, and that NUL in text, math and code cannot produce a separator.

@PathGao
PathGao added this pull request to stack #1026 October 23, 2026 20:22
Base automatically changed from perf/restore-active-tab-only to master October 24, 2026 05:36
…ocks

With `splitBlocks: true`, `render_markdown` writes `\0` on a line of its
own between the top-level blocks of the document, so the preview can diff,
process and sanitize block by block instead of the whole document after
every typing pause. comrak still parses the whole document, so footnote
numbers, reference links and heading-id dedupe are in each block's markup.

No separator goes between footnote definitions (one `<section>`), nor
inside a raw HTML element left open across blocks (`<details>` / markdown /
`</details>`). No document can write the separator: comrak turns NUL into
U+FFFD, and the math restore now does the same.

Without the flag the output is byte-for-byte what it was; the export and
the frontend do not pass it yet.
@PathGao
PathGao force-pushed the perf/split-blocks-rust branch from 439317b to ca76487 Compare October 24, 2026 05:36
@PathGao
PathGao merged commit 38850c2 into master Oct 24, 2026
3 of 4 checks passed
@PathGao
PathGao deleted the perf/split-blocks-rust branch October 24, 2026 06:04
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