Long markdown code block not fully loaded in Live Preview

Steps to Reproduce

  1. Create a new plugin from the template or just use any existing plugin and copy the following code to onload function
this.registerMarkdownCodeBlockProcessor('test', (source, el, ctx) => {
	console.log('start processing test...');
	// present the diff directly in the editor
	el.insertAdjacentHTML('afterbegin', '<div><p>TEST</p></div>');
});
  1. Load the plugin in Obsidian and create a new file with its content a title and a “test” code block
  2. Type a line with several chars in the code block, e.g. a to z as I used. Then copy and paste this line 1000 times (type dd1000P in vim mode). Now your markdown file looks like
# This is a test
```test
abcdefghijklmnopqrstuvwxyz
... (repeat 1000 times)
abcdefghijklmnopqrstuvwxyz
```
  1. Close and reopen that file in live preview mode.

Expected Result

After reopening the file, the cursor is located at the header line, the code block should be replaced by an HTML div block with the word “TEST”, and the console output should contain the string “start processing test…”.

Actual Result

Neither the code block changed nor the console output.

Live Preview

Meanwhile, if we just scroll the text to a position near the end of the code block, the expected result appears. However, the issue remains after we close and reopen the file.

Live Preview after Scrolling

Since we did not do anything other than scroll the text, it seems like the content of the code block was not fully loaded until we scrolled the text to the end of the block.

If we change to the reading view, we can directly get the expected result, even if we remain the live preview in a split window.

Live Preview and Reading View

Additional Information

Although I can scroll over 500 lines with the issue remaining, I will get the expected result if I only create a block with 500 lines.

Scroll over 500 lines in Live Preview

This issue affects lots of plugins that use the funtion registerMarkdownCodeBlockProcessor. I have tested QAMichaelPeng/obsidian-graphviz and gregzuro/obsidian-kroki with the example Linux Kernel Diagram given by Graphviz, and artisticat1/obsidian-tikzjax with the example Poincare Diagram posted on Texample.

I also find that @Hajiku shares a very similar issue which deals with ‘PosterProcessor’.

P.S. Every time before I close and reopen the file, I clear the console in order not to count the number of the string occurrences.

Environment

SYSTEM INFO:
	Obsidian version: v1.1.9
	Installer version: v1.0.0
	Operating system: Darwin Kernel Version 22.2.0: Fri Nov 11 02:03:51 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T6000 22.2.0
	Login status: not logged in
	Insider build toggle: off
	Live preview: on
	Legacy editor: off
	Base theme: dark
	Community theme: none
	Snippets enabled: 1
	Restricted mode: off
	Plugins installed: 8
	Plugins enabled: 1
		1: Graphviz Plugin v0.0.1
1 Like

I have the same problem.
my plugin calls document.querySelector(".workspace-leaf.mod-active .markdown-reading-viewé) and then looks for its innerHTML. But for long pages it is uncomplete and contains only the beginning.
Is there a way to force obsidian to load the full preview page html (which is probably unpossible because several plugins might modify it at any time), or at least to give an inner html that would be a complete “snapshot” of the full page at the time of the call, and not just the 10000 first characters or so ?
thank tou !

Problem Cause

After long research in the optimized code in the DevTools, I found that the problem arises in two ways: firstly, the extension made by StateField does not actually apply to the whole document as it is described on the official website, and secondly, there are two different ways of processing the code when parsing to the beginning and end of the code block, and the real post-processing only starts when parsing to the end of the block. The combination of these two problems leads to the fact that when faced with a long code block, all post-processing of the CodeBlock will not start if the document is not navigated near the end.

Solution

The solution is very simple: browse to the end of the code block while browsing to its beginning, and here is a simple way to implement it. First, we should record the beginning of the code block:

const view = tr.state.field(editorEditorField);
let execCodeBlocks: {from: number, to: number, fence: string}[] = [];

for (let { from, to } of view.visibleRanges) {
    syntaxTree(view.state).iterate({
        from, to, enter(node) {
            if (node.type.name.contains('codeblock-begin')) {
                const cb_str = view.state.sliceDoc(node.from, node.to);
                let cb_tokens = cb_str.match(/(`{3,}|~{3,})[ \t]*(exec-)([\w\/+#-]*)/);
                if (cb_tokens) {
                    execCodeBlocks.push({
                        from: node.from,
                        to: node.to,
                        fence: cb_tokens[1],
                    });
                }
            }
        }
    })
}

The regular expression I use here is to distinguish between the normal code block and the code block with post-processing.
Then, we should find the end of the code block.

const state = tr.state;
const selection = state.selection;
const doc = state.doc;
const fenceFrom = execCodeBlock.from;
execCodeBlock.from = execCodeBlock.to;
execCodeBlock.to = doc.length;

let cursor = new SearchCursor(doc, execCodeBlock.fence, execCodeBlock.from);
if (cursor.next()) {
    execCodeBlock.to = cursor.value.from;
}

And finally, replace the execCodeBlock with blank.

const builder = new RangeSetBuilder<Decoration>();
// parse and find, and maybe in a loop
builder.add(
    execCodeBlock.from,
    execCodeBlock.to,
    Decoration.replace({ block: true })
);

However, we don’t want the code block to be collapsed all the time, we need to be able to edit the code when the cursor moves over the code block.

const fenceTo = cursor.value.to;
// at least one char in code block
if (execCodeBlock.to == execCodeBlock.from + 1) continue;
execCodeBlock.from += 1;
execCodeBlock.to -= 1;
// repair for an unknown bug
if (execCodeBlock.to - execCodeBlock.from <= 1000) continue;
// selection intersect
if (selection.ranges.some(range => {
    return range.from <= fenceTo && range.to >= fenceFrom;
}) continue;

builder.add(...);

If we remove the line “repair for an unknown bug”, all functions will work properly, except for code blocks with fewer than 10 characters but more than 2 lines that will render incorrectly. Specifically, when expanding such code blocks, all lines are rendered as normal text with only the first line rendered as if it is code. But since I only want to deal with very long code blocks, I’ll just exclude this case here.

Result

Collapsed

Expanded

P.S. I didn’t add post-processing here because the effect can’t be shown directly from the image, but the examples I tested myself show that it works fine. Also, I’m not sure if my solution is applicable to the problem @Serdj encountered.

1 Like