I’m trying to write an extension for Pandoc-style fenced divs, but I have been unsuccessful so far.
For the purpose of the example, let’s assume that such a div consists of three parts:
- An opening fence, which is a line started with
:::
followed by an attribute block{...}
; - Followed by the div contents,
- Followed by a closing fence, which is a line consisting of 3 colons.
If I understand correctly, we need a composite block parser for this. The following is my attempt:
import type {MarkdownConfig, BlockParser, BlockContext, Line} from '@lezer/markdown';
const fencedDivOpeningRegex = /^::: {.*}$/;
class FencedDivParser implements BlockParser {
name = "FencedDiv";
parse(cx: BlockContext, line: Line): boolean | null {
if (! fencedDivOpeningRegex.test(line.text)) {
return false;
}
const firstFenceStart = cx.lineStart + line.pos;
const firstFenceEnd = firstFenceStart + line.text.length;
cx.startComposite("FencedDiv", line.pos);
cx.addElement(cx.elt("FencedDivFence", firstFenceStart, firstFenceEnd));
cx.nextLine();
cx.startComposite("FencedDivContents", line.pos);
return null;
}
}
export const extension: MarkdownConfig = {
defineNodes: [
{
name: "FencedDiv",
block: true,
composite: (cx, line) => {
return (line.text.trim() !== ':::');
},
},
{
name: "FencedDivContents",
block: true,
composite: (cx, line) => {
return (line.text.trim() !== ':::');
},
},
{
name: "FencedDivFence",
},
],
parseBlock: [
new FencedDivParser(),
],
}
For the sake of simplicity, I am only using 3 node types (the whole block, the fences, and the block contents).
Unfortunately, the parser does not work correctly in all cases. For example, with this input:
::: {.test}
I am a fenced div
:::
I am not part of the fenced div
both the FencedDiv and FencedDivContents extend to the end of the document. When I add a newline before the closing fence, it considers the FencedDiv and FencedDivContents to end before the fence.
I am probably misunderstanding how the composite()
method should work. In all the examples I found online, it keeps track of indentation, and uses that information to end blocks. We can’t use indentation here. Another complicating factor is that fenced divs can be nested, so you don’t want to close all open divs as soon as the first closing fence is encountered.
Any help would be much appreciated!