From d149bc1dfd8466ba6a6175c14a9a0c9b604c9490 Mon Sep 17 00:00:00 2001 From: Eric Rykwalder Date: Wed, 9 Mar 2022 16:55:54 -0500 Subject: [PATCH] add tex parsing --- __tests__/extensions.ts | 57 +++++++++++++++++++++++++++++++++ src/extensions.ts | 70 +++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + 3 files changed, 128 insertions(+) diff --git a/__tests__/extensions.ts b/__tests__/extensions.ts index 38e0e29..6679eba 100644 --- a/__tests__/extensions.ts +++ b/__tests__/extensions.ts @@ -28,6 +28,9 @@ const specParser = new SpecParser(parser, { YF: "YAMLFrontMatter", ym: "YAMLMarker", yc: "YAMLContent", + XB: "TexBlock", + XI: "TexInline", + xm: "TexMarker", }); /* @@ -189,6 +192,60 @@ Line 5} ` ); + test( + "Tex Block (End Leaf)", + ` +{P:plaintext} +{XB:{xm:$$}tex content +- still tex +> and still tex +{xm:$$}} + ` + ); + + test( + "Tex Block (Tailing Text)", + ` +{XB:{xm:$$}tex content{xm:$$}} {P:plaintext} + ` + ); + + test( + "Tex Block (Unterminated)", + ` +{XB:{xm:$$}tex content + +# this is never terminated + }` + ); + + test( + "Tex Inline (1)", + ` +{P:plaintext {XI:{xm:$}tex content{xm:$}} more plaintext} + +{P:{XI:{xm:$}1.234{xm:$}}} + ` + ); + + test( + "Tex Inline (2)", + ` +{P:$not tex$1.234} + +{P:$also not tex $} + +{P:$ still not tex$} + +{P:{XI:{xm:$}actually tex +{xm:$}}} + +{P:{XI:{xm:$}tex +more text +{xm:$}}} + ` + ); + test( "YAMLFrontMatter", ` diff --git a/src/extensions.ts b/src/extensions.ts index 0309d32..2b23783 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -317,6 +317,75 @@ export const TaskList: MarkdownConfig = { }; /* End Copyright */ +export const Tex: MarkdownConfig = { + defineNodes: ["TexBlock", "TexInline", "TexMarker"], + parseBlock: [ + { + name: "TexBlock", + endLeaf: (_, line: Line) => + line.text.slice(line.pos, line.pos + 2) == "$$", + // This is an imperfect match for HyperMD, because + // in HyperMD the block can start even in inline content. + parse(cx: BlockContext, line: Line) { + if (line.text.slice(line.pos, line.pos + 2) != "$$") { + return false; + } + const start = cx.lineStart + line.pos; + const markers = [cx.elt("TexMarker", start, start + 2)]; + const regex = /(^|[^\\])\$\$/; + let remaining = line.text.slice(line.pos + 2); + let startOffset = 2; + let match; + while (!(match = regex.exec(remaining)) && cx.nextLine()) { + remaining = line.text; + startOffset = 0; + } + let end; + if (match) { + const lineEnd = match.index + match[0].length + startOffset; + end = cx.lineStart + lineEnd; + markers.push(cx.elt("TexMarker", end - 2, end)); + if ( + lineEnd == line.text.length || + /^\s+$/.test(line.text.slice(lineEnd)) + ) { + cx.nextLine(); + } else { + line.pos = line.skipSpace(lineEnd); + } + } else { + end = cx.lineStart + line.text.length; + } + cx.addElement(cx.elt("TexBlock", start, end, markers)); + return true; + }, + }, + ], + parseInline: [ + { + name: "TexInline", + parse(cx: InlineContext, next: number, pos: number) { + let match = /^\$[^$\s]/.exec(cx.text.slice(pos - cx.offset)); + if (!match) { + return -1; + } + const start = pos; + match = /[^\t \\]\$(\D|$)/.exec(cx.text.slice(pos - cx.offset + 1)); + if (!match) { + return -1; + } + const end = start + 1 + match.index + 2; + return cx.addElement( + cx.elt("TexInline", start, end, [ + cx.elt("TexMarker", start, start + 1), + cx.elt("TexMarker", end - 1, end), + ]) + ); + }, + }, + ], +}; + export const YAMLFrontMatter: MarkdownConfig = { defineNodes: ["YAMLFrontMatter", "YAMLMarker", "YAMLContent"], parseBlock: [ @@ -357,6 +426,7 @@ export const ObsidianMDExtensions = [ Strikethrough, Table, TaskList, + Tex, YAMLFrontMatter, ]; diff --git a/src/index.ts b/src/index.ts index 1e011bf..b54bba3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,5 +5,6 @@ export { ObsidianMDExtensions, parser, TaskList, + Tex, YAMLFrontMatter, } from "./extensions";