Repository navigation
fix: highlight only the code blocks an edit changes - #678
Merged
Merged
Conversation
…corations # Conflicts: # CHANGELOG.md
…corations # Conflicts: # CHANGELOG.md
Azganoth
enabled auto-merge (squash)
October 10, 2026 19:30
This was referenced Oct 10, 2026
Azganoth
added a commit
that referenced
this pull request
Oct 10, 2026
## Summary After each edit, once typing paused for 200 ms, Milkdown's listener plugin serialized the whole document so the session held a current copy of its Markdown, and it serialized once more when the editor was created. That blocked the main thread for about a third of a second in a 200 KB document of tables and about a second at 500 KB (#672). The listener is removed. The session's `content` is now the Markdown a document was opened, created, or last saved with, and each reader that needs the current text gets it from the editor: - Save and moving the open document already read the editor bridge first and are unchanged. - Reopening the same path reloads the editor whenever the document has edits, not only when the file's text differs. The edits were discarded to reopen it, and the session's text no longer shows them. - An editor that lets go of an edited document that stays open hands its Markdown to the session once. A remounted editor starts from that text, and Save still writes the edits after a crash in the document surface removes the editor, because the menu bar sits outside that error boundary. - Folder search treated the session's text changing as its cue to search the open document again. It now waits 200 ms after a content-change signal that the editor bridge fires on each edit without reading the text. The bridge's `getMarkdown` returns `null` instead of throwing while the editor is still being created, so these readers fall back to the session's text, which no edit can have changed yet. ## Related Issue Closes #672 ## Verification - `openSession`: an edited document whose session text still matches the file reloads when reopened after its edits are discarded. - `document-screen`: edits fire the content signal without changing the session's text; when the editor unmounts, the edited Markdown moves to the session and the next editor mounts with it. - `documentWorkflows`: Save writes the handed-over text once no editor holds the document, and a clean or replaced document's editor is not read. - `folderSearchWorkflows`: the open document's results follow an edit once it settles. - Each of these tests failed with its fix disabled. The editor tests of the listener were removed; the dirty-tracking assertions they held remain. - `pnpm check:frontend` passed after merging `main`. Manually verified in a Windows 11 release build, before merging #675–#678 from `main`: 1. In 205 KB and 505 KB documents of 120-row, 6-column tables, five spaced keystrokes in a cell of the middle table were each followed by no long animation frame. The keystroke's own frame, before the character painted, took 69–85 ms at 205 KB and 181–226 ms at 505 KB, mostly the input handler. 2. The same harness on `main` at 7cea7b9 recorded a 636–1179 ms timer frame 230–290 ms after every keystroke at 205 KB. Not verified: the release-build timings after merging #675–#678.
Azganoth
added a commit
that referenced
this pull request
Oct 10, 2026
## Summary While code line numbers were shown, every document change rebuilt the line-number decorations of every code block in one new `DecorationSet`, which costs top-level blocks times decorations. Typing anywhere in a document with much code slowed with the square of its code, as highlighting did before #678. - #678's incremental update moves out of the highlight plugin into a shared `codeBlockDecorations` utility, and both plugins use it: the set is built once, then each transaction maps it and decorates again only the code blocks in its changed ranges, plus every code block inside a node whose own boundary it changed. Highlighting behaves as before. - With line numbers shown, an edit outside code does no numbering work and an edit inside a block renumbers only that block. Turning the setting on still numbers every block, and turning it off clears them. - `docs/architecture.md` describes the line numbers alongside the incremental highlighting, and the existing `CHANGELOG.md` entry for faster typing in code-heavy documents now covers line numbers. ## Related Issue Closes #679 ## Verification - Focused tests compare the line-number decorations with a full rebuild after edits inside, between, and across code blocks, splits and joins, conversions between code and paragraph, wrapping in and lifting out of a quote, whole-document replacement, and toggling the setting with edits in between, and they assert which blocks are renumbered. Rebuilding the whole set, skipping the removal of stale decorations, or skipping the code blocks inside a node whose boundary changed each makes them fail. The highlight tests pass unchanged through the shared utility. - `pnpm check:frontend` passed. Manually verified in a happy-dom test mount on Windows 11, with a temporary probe that was not committed: 1. The fixture was about 1 MB of fenced code: roughly 1,030 30-line excerpts of the repository's TypeScript, Rust, and CSS, each after a one-sentence paragraph. Each keystroke's transaction was applied to the editor state seven times at the end of the middle paragraph and seven times inside the middle code block. | Keystroke | `main` at fa9e842 | This branch | | --- | --- | --- | | Paragraph, line numbers shown | 163–476 ms | 9–19 ms | | Code block, line numbers shown | 158–182 ms | 9–12 ms | | Either, line numbers hidden | 6–24 ms | 6–14 ms | Not verified: keystroke times in a release build.
This was referenced Oct 10, 2026
Azganoth
added a commit
that referenced
this pull request
Oct 12, 2026
…em (#693) ## Summary Every document change rebuilt the heading fold decorations of the whole document, folded or not: it walked every heading, resolved each one to see whether it has a section, and built one new `DecorationSet`. In a document with many headings this was the largest Leafdown-owned part of a keystroke. - The fold plugin now maps its decorations through each transaction and keeps them in two layers. A heading's marker and folded indicator depend only on the heading, its next sibling, and whether it is folded, so they are decorated again only for the textblocks a change touches, the block just before each change, headings inside a node whose own boundary it changed, and headings folded or unfolded. The blocks a folded section hides are rebuilt only when the folds change, a folded heading is deleted, or a change reaches a folded section or a node holding one. - Typing in a paragraph creates no fold decorations, with or without folded headings. Folding, unfolding, revealing, and the outline's view of folded headings behave as before. - The changed-range collection from #678 moves into a shared `changedRanges` utility that the code block decorations also use. - `CHANGELOG.md` records the faster typing in documents with many headings. ## Related Issue Closes #684 ## Verification - Focused tests compare the fold decorations with a full rebuild after edits that create, remove, split, and join headings, change a heading's level, give a heading a section or take it away, wrap blocks in a quote and lift them out, insert and remove content inside folded sections, combine several steps, and replace the whole document, each with and without folded headings. They also assert that typing in a paragraph creates no decorations and that folding decorates only the folded heading and its section. Removing any one rule that selects what to re-decorate makes them fail. - `pnpm check:frontend` passed. Manually verified in a Node test on Windows 11, with a temporary probe that was not committed: 1. The 5 MB dense fixture from #680, parsed by the editor into 11,860 top-level blocks and 3,177 headings, held an editor state with only the fold plugin. A character was typed 30 times in a paragraph near the middle. The fold plugin's update took a median of 92 ms per keystroke on this branch, against 348 ms for the full rebuild every edit ran on `main` at 9aa80f9. Not verified: keystroke paint times in a release build. ## Notes The remaining 92 ms is ProseMirror's `DecorationSet.map`, which validates each node decoration on a top-level block with a linear search of the document's top-level children, so it costs headings times top-level blocks and falls with the square of the document's size. Removing it would take a heading node view or a decoration source other than a `DecorationSet`, which is outside this change.
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.
Summary
On every document change,
prosemirror-highlight0.15.3 (under@milkdown/plugin-highlight) gathered every code block's tokens and built one newDecorationSetfrom them, which costs top-level blocks times tokens. Typing anywhere in a document with much code slowed with the square of its code.@milkdown/plugin-highlightstays as the source of the ShikicreateParser.docs/architecture.mddescribes the incremental highlighting, andCHANGELOG.mdrecords the faster typing under Fixed.Related Issue
Closes #673
Verification
setNodeMarkupandsetNodeAttribute, conversions between code and paragraph, wrapping in and lifting out of a quote, multi-step transactions, and whole-document replacement, and they assert which blocks the parser runs for. Disabling the wrapped-node rule, the removal of stale decorations, or the attribute-step range each makes them fail.pnpm check:frontendpassed.Manually verified in release builds on Windows 11, under a separate application identity in a 1280×900 window, driven through the WebView2 DevTools protocol:
The fixture was 1 MB of fenced code: 930 30-line excerpts of the repository's TypeScript, Rust, and CSS, each after a one-sentence paragraph. Each run opened it in a fresh process and typed
xfive times at the end of the middle top-level paragraph, then five times at the end of the middle code block, waiting for 1.5 s without long frames after each keystroke. Two runs ofmainat 7cea7b9 were interleaved with two of this branch; figures are each run's median keystroke.mainCharacters typed into the code block were highlighted with Shiki's light and dark colours in the release build.
Not verified: opening times were not compared beyond noting they stayed within the same noisy 12.6–19.7 s range on both builds.
Notes
Both builds predate #677, so their paint times include more than 1 s of forced layout per keystroke from the block gutters it since removed, and the machine slowed by up to twice between runs. The frame time without forced layout is the stable comparison.
codeLineNumbers.tsalso rebuilds its whole decoration set on every edit while code line numbers are shown. It is left unchanged here.