-
Notifications
You must be signed in to change notification settings - Fork 38.2k
Description
Type: Bug
Summary
vscode/extensions/markdown-language-features/preview-src/scroll-sync.ts uses a binary search in getLineElementsAtPageOffset to map preview scroll positions back to editor line numbers. This algorithm assumes that all code-line elements have monotonically increasing bounding boxes.
When some lines are wrapped inside an element with display:none, their bounding boxes become (top = 0, height = 0). This breaks ordering assumptions and causes the binary search to return incorrect elements, resulting in scroll‑sync mismatches.
| export function getLineElementsAtPageOffset(offset: number, documentVersion: number): { previous: CodeLineElement; next?: CodeLineElement } { | |
| const lines = getCodeLineElements(documentVersion).filter(x => x.isVisible); | |
| const position = offset - window.scrollY; | |
| let lo = -1; | |
| let hi = lines.length - 1; | |
| while (lo + 1 < hi) { | |
| const mid = Math.floor((lo + hi) / 2); | |
| const bounds = getElementBounds(lines[mid]); | |
| if (bounds.top + bounds.height >= position) { | |
| hi = mid; | |
| } | |
| else { | |
| lo = mid; | |
| } | |
| } |
Steps to Reproduce
<div class="lang-ko">
- First page Korean AAA
- First page Korean BBB
- First page Korean CCC
- First page Korean DDD
</div>
<div class="lang-en" style="display: none">
- First page English AAA
- First page English BBB
- First page English CCC
- First page English DDD
</div>
---
<div class="lang-ko">
- Second page Korean AAA
- Second page Korean BBB
- Second page Korean CCC
- Second page Korean DDD
</div>
<div class="lang-en" style="display: none">
- Second page English AAA
- Second page English BBB
- Second page English CCC
- Second page English DDD
</div>
---
<div class="lang-ko">
- Third page Korean AAA
- Third page Korean BBB
- Third page Korean CCC
- Third page Korean DDD
</div>
<div class="lang-en" style="display: none">
- Third page English AAA
- Third page English BBB
- Third page English CCC
- Third page English DDD
</div>
- Open Markdown preview.
- Move the cursor in the editor or double‑click inside the preview.
- Initially, scroll sync may appear to work, but after a few cursor moves or repeated clicking in the preview, the mapping gradually becomes increasingly incorrect and eventually jumps to wrong lines after the hidden section.
Expected Behavior
- Hidden elements should not participate in scroll‑sync calculations.
- Scroll sync should remain correct even if parts of the DOM are visually removed.
Proposed Fix
Related discussions:
- markdown preview: trouble with <details> blocks #175353
- Scroll sync not working properly for collapsed markdown details blocks #176538
- Fix for
<details>visibility: 71504b5- However, only
<details>was patched; other hidden elements still break scroll‑sync.
- However, only
Add a visual‑presence filter before binary search:
function isVisuallyPresent(line: CodeLineElement): boolean {
const style = window.getComputedStyle(line.element);
if (style.display === 'none' || style.visibility === 'hidden') {
return false;
}
const bounds = getElementBounds(line);
return bounds.height > 0 || bounds.width > 0;
}
const lines = getCodeLineElements(documentVersion)
.filter(x => x.isVisible && isVisuallyPresent(x));If calling getComputedStyle is considered too expensive, the check could rely solely on bounding‑box validation (maybe?).
I am not familiar with contributing to VSCode, and the fix described above is only a conceptual suggestion rather than a fully working implementation. I am sharing this analysis in case it may help guide a potential improvement 😄
VS Code version: Code 1.106.3 (bf9252a, 2025-11-25T22:28:18.024Z)
OS version: Windows_NT x64 10.0.26100
Modes:
System Info
| Item | Value |
|---|---|
| CPUs | 12th Gen Intel(R) Core(TM) i9-12900H (20 x 2918) |
| GPU Status | 2d_canvas: enabled direct_rendering_display_compositor: disabled_off_ok gpu_compositing: enabled multiple_raster_threads: enabled_on opengl: enabled_on rasterization: enabled raw_draw: disabled_off_ok skia_graphite: disabled_off trees_in_viz: disabled_off video_decode: enabled video_encode: enabled vulkan: disabled_off webgl: enabled webgl2: enabled webgpu: enabled webnn: disabled_off |
| Load (avg) | undefined |
| Memory (System) | 63.71GB (26.37GB free) |
| Process Argv | --disable-extensions --crash-reporter-id 759dca4a-1c6b-4855-9446-e749f4614f70 |
| Screen Reader | no |
| VM | 0% |