Skip to content

Add source map for every block-level element in the Markdown preview #133376

@Lemmingh

Description

@Lemmingh

Issue Type: Bug

Does this issue occur when all extensions are disabled?: Yes

  • VS Code Version: 1.61.0-insider (system setup) (a0af581)
  • OS Version: Windows_NT x64 10.0.19043

I searched the Issues by https://github.com/microsoft/vscode/issues?q=is%3Aissue+label%3Amarkdown+scroll, found some cases with similar behavior, but couldn't recognize one as the same. So, I open this issue in case a root cause of bugs has't been revealed.

Steps to Reproduce

  1. Create a Markdown editor, and fill it with one of the samples below.

  2. Adjust the window size, so that no more than 30 lines are visible in the editor.

  3. "Open Preview to the Side".

  4. Scroll the preview.

Actual behavior:

The editor is scrolled strangely, or jumps to an unexpected position, or whatever.

Expected behavior:

The editor and the preview should be synchronized, all or most of the time.

Problem

The Markdown preview only sets source map for a few kinds of elements, which can lead to severely inaccurate scrolling:

for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'fence', 'blockquote_open', 'list_item_open']) {
this.addLineNumberRenderer(md, renderName);
}

It can be observed in many conditions, such as:

  • Scrolling the preview in order to find something in the editor.

  • Editing a long document when the preview is open.

Solution

Add source map for every element where possible.

Here is a solution. I've tested it by adding a Markdown extension.

First, internally create a markdown-it plugin in the markdownEngine.ts:

import type { PluginSimple } from "markdown-it";

/**
 * Adds begin line index to the output via the "data-line" data attribute.
 */
const pluginSourceMap: PluginSimple = (md): void => {
    // Set the attribute on every possible token.
    md.core.ruler.push("data_attribute_source_map", (state): any => {
        for (const token of state.tokens) {
            if (token.map && token.type !== "inline") {
                token.attrSet("data-line", String(token.map[0]));
                token.attrJoin("class", "code-line");
            }
        }
    });

    // The "html_block" renderer doesn't respect `attrs`. We need to insert a marker.
    const originalHtmlBlockRenderer = md.renderer.rules["html_block"];
    if (originalHtmlBlockRenderer) {
        md.renderer.rules["html_block"] = (tokens, idx, options, env, self) => {
            return (
                `<div ${self.renderAttrs(tokens[idx])} ></div>\n` +
                originalHtmlBlockRenderer(tokens, idx, options, env, self)
            );
        };
    }
};

Then, load it just before the return:

md.use(pluginSourceMap);

Known limitations:

Some markdown-it plugins do not respect attrs, thus, their output won't get source map.

Sample 1

From:

Repeat this piece 10 times:

<section>
a<br>
a
a
a
a
a
a
a
</section>

Sample 2

From:

Begin the document with:

#

Then, add 99 blank lines.

Next, add:

|a|b|
| --- | --- |

Finally, repeat this line 99 times:

|c|d|

Metadata

Metadata

Assignees

Labels

bugIssue identified by VS Code Team member as probable bughelp wantedIssues identified as good community contribution opportunitiesinsiders-releasedPatch has been released in VS Code InsidersmarkdownMarkdown support issuesverifiedVerification succeeded

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions