import { BlockContext, Element, LeafBlock, LeafBlockParser, Line, MarkdownConfig, tags as t, } from "../deps.ts"; // Forked from https://github.com/lezer-parser/markdown/blob/main/src/extension.ts // MIT License // Author: Marijn Haverbeke // Change made: Avoid wiki links with aliases [[link|alias]] from being parsed as table row separators function parseRow( cx: BlockContext, line: string, startI = 0, elts?: Element[], offset = 0, ) { let count = 0, first = true, cellStart = -1, cellEnd = -1, esc = false; let parseCell = () => { elts!.push( cx.elt( "TableCell", offset + cellStart, offset + cellEnd, cx.parser.parseInline( line.slice(cellStart, cellEnd), offset + cellStart, ), ), ); }; let inWikilink = false; for (let i = startI; i < line.length; i++) { let next = line.charCodeAt(i); if (next === 91 /* '[' */ && line.charAt(i + 1) === "[") { inWikilink = true; } else if ( next === 93 /* ']' */ && line.charAt(i - 1) === "]" && inWikilink ) { inWikilink = false; } if (next == 124 /* '|' */ && !esc && !inWikilink) { if (!first || cellStart > -1) count++; first = false; if (elts) { if (cellStart > -1) parseCell(); elts.push(cx.elt("TableDelimiter", i + offset, i + offset + 1)); } cellStart = cellEnd = -1; } else if (esc || next != 32 && next != 9) { if (cellStart < 0) cellStart = i; cellEnd = i + 1; } esc = !esc && next == 92; } if (cellStart > -1) { count++; if (elts) parseCell(); } return count; } function hasPipe(str: string, start: number) { for (let i = start; i < str.length; i++) { let next = str.charCodeAt(i); if (next == 124 /* '|' */) return true; if (next == 92 /* '\\' */) i++; } return false; } const delimiterLine = /^\|?(\s*:?-+:?\s*\|)+(\s*:?-+:?\s*)?$/; class TableParser implements LeafBlockParser { // Null means we haven't seen the second line yet, false means this // isn't a table, and an array means this is a table and we've // parsed the given rows so far. rows: false | null | Element[] = null; nextLine(cx: BlockContext, line: Line, leaf: LeafBlock) { if (this.rows == null) { // Second line this.rows = false; let lineText; if ( (line.next == 45 || line.next == 58 || line.next == 124 /* '-:|' */) && delimiterLine.test(lineText = line.text.slice(line.pos)) ) { let firstRow: Element[] = [], firstCount = parseRow(cx, leaf.content, 0, firstRow, leaf.start); if (firstCount == parseRow(cx, lineText, line.pos)) { this.rows = [ cx.elt( "TableHeader", leaf.start, leaf.start + leaf.content.length, firstRow, ), cx.elt( "TableDelimiter", cx.lineStart + line.pos, cx.lineStart + line.text.length, ), ]; } } } else if (this.rows) { // Line after the second let content: Element[] = []; parseRow(cx, line.text, line.pos, content, cx.lineStart); this.rows.push( cx.elt( "TableRow", cx.lineStart + line.pos, cx.lineStart + line.text.length, content, ), ); } return false; } finish(cx: BlockContext, leaf: LeafBlock) { if (!this.rows) return false; cx.addLeafElement( leaf, cx.elt( "Table", leaf.start, leaf.start + leaf.content.length, this.rows as readonly Element[], ), ); return true; } } /// This extension provides /// [GFM-style](https://github.github.com/gfm/#tables-extension-) /// tables, using syntax like this: /// /// ``` /// | head 1 | head 2 | /// | --- | --- | /// | cell 1 | cell 2 | /// ``` export const Table: MarkdownConfig = { defineNodes: [ { name: "Table", block: true }, { name: "TableHeader", style: { "TableHeader/...": t.heading } }, "TableRow", { name: "TableCell", style: t.content }, { name: "TableDelimiter", style: t.processingInstruction }, ], parseBlock: [{ name: "Table", leaf(_, leaf) { return hasPipe(leaf.content, 0) ? new TableParser() : null; }, endLeaf(cx, line, leaf) { if ( leaf.parsers.some((p) => p instanceof TableParser) || !hasPipe(line.text, line.basePos) ) return false; // @ts-ignore: internal let next = cx.scanLine(cx.absoluteLineEnd + 1).text; return delimiterLine.test(next) && parseRow(cx, line.text, line.basePos) == parseRow(cx, next, line.basePos); }, before: "SetextHeading", }], };