diff --git a/__tests__/extensions.ts b/__tests__/extensions.ts index 6679eba..210d958 100644 --- a/__tests__/extensions.ts +++ b/__tests__/extensions.ts @@ -11,6 +11,8 @@ const specParser = new SpecParser(parser, { T: "Task", t: "TaskMarker", /* End Copyright */ + C: "Comment", + cm: "CommentMarker", EM: "Embed", eM: "EmbedMark", H: "Hashtag", @@ -50,6 +52,43 @@ function test(name: string, spec: string, p = parser, only = false) { /* End Copyright */ describe("Obsidian Extension", () => { + test( + "Comment (End Leaf)", + ` +{P:plaintext} +{C:{cm:%%}comment +- still comment +> and still comment +{cm:%%}} + ` + ); + + test( + "Comment (Tailing Text)", + ` +{C:{cm:%%}comment{cm:%%}} {P:plaintext} + ` + ); + + test( + "Comment (Unterminated)", + ` +{C:{cm:%%}comment + +# this is never terminated + }` + ); + + test( + "Comment Inline (1)", + ` +{P:plaintext {C:{cm:%%}comment{cm:%%}} more plaintext} + +{P:plaintext %%not a comment +more %% plaintext} + ` + ); + test( "Footnotes", ` diff --git a/src/extensions.ts b/src/extensions.ts index 2b23783..fe34053 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -19,6 +19,70 @@ declare module "@lezer/markdown" { } } +export const Comment: MarkdownConfig = { + defineNodes: ["Comment", "CommentMarker"], + parseBlock: [ + { + name: "CommentBlock", + endLeaf: (cx, line: Line) => { + return line.text.slice(line.pos, line.pos + 2) == "%%"; + }, + 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("CommentMarker", 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("CommentMarker", 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("Comment", start, end, markers)); + return true; + }, + }, + ], + parseInline: [ + { + name: "CommentInline", + parse(cx: InlineContext, next: number, pos: number) { + let match = /^%%[^\n]*[^\n\\]%%/.exec(cx.text.slice(pos - cx.offset)); + if (!match) { + return -1; + } + const start = pos; + const end = pos + match[0].length; + return cx.addElement( + cx.elt("Comment", start, end, [ + cx.elt("CommentMarker", start, start + 2), + cx.elt("CommentMarker", end - 2, end), + ]) + ); + }, + }, + ], +}; + class FootnoteReferenceParser implements LeafBlockParser { constructor(private labelEnd: number) {} @@ -420,6 +484,7 @@ export const YAMLFrontMatter: MarkdownConfig = { }; export const ObsidianMDExtensions = [ + Comment, Footnote, Hashtag, InternalLink, diff --git a/src/index.ts b/src/index.ts index b54bba3..0c3c665 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export { + Comment, Footnote, Hashtag, InternalLink,