Fixes #304
parent
a170d9c2d7
commit
285b52ebef
|
@ -22,6 +22,7 @@ export { readAll } from "https://deno.land/std@0.165.0/streams/conversion.ts";
|
|||
|
||||
export type {
|
||||
BlockContext,
|
||||
Element,
|
||||
LeafBlock,
|
||||
LeafBlockParser,
|
||||
Line,
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
StreamLanguage,
|
||||
Strikethrough,
|
||||
styleTags,
|
||||
Table,
|
||||
tags as t,
|
||||
TaskList,
|
||||
yamlLanguage,
|
||||
|
@ -178,6 +177,7 @@ const directiveStart = /^\s*<!--\s*#([a-z]+)\s*(.*?)-->\s*/;
|
|||
const directiveEnd = /^\s*<!--\s*\/(.*?)-->\s*/;
|
||||
|
||||
import { parser as directiveParser } from "./parse-query.js";
|
||||
import { Table } from "./table_parser.ts";
|
||||
|
||||
const highlightingDirectiveParser = directiveParser.configure({
|
||||
props: [
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
import {
|
||||
BlockContext,
|
||||
Element,
|
||||
LeafBlock,
|
||||
LeafBlockParser,
|
||||
Line,
|
||||
MarkdownConfig,
|
||||
tags as t,
|
||||
} from "../deps.ts";
|
||||
|
||||
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",
|
||||
}],
|
||||
};
|
Loading…
Reference in New Issue