diff --git a/src/nextjournal/markdown/impl.clj b/src/nextjournal/markdown/impl.clj index e600553..ae460d8 100644 --- a/src/nextjournal/markdown/impl.clj +++ b/src/nextjournal/markdown/impl.clj @@ -1,7 +1,6 @@ ;; # 🧩 Parsing (ns nextjournal.markdown.impl - (:require [clojure.set :as set] - [clojure.zip :as z] + (:require [clojure.zip :as z] [nextjournal.markdown.impl.extensions :as extensions] [nextjournal.markdown.impl.types :as t] [nextjournal.markdown.impl.utils :as u]) @@ -32,8 +31,9 @@ HardLineBreak HtmlInline Image - HtmlBlock) - (org.commonmark.parser Parser))) + HtmlBlock + SourceSpan) + (org.commonmark.parser Parser IncludeSourceSpans))) (set! *warn-on-reflection* true) @@ -46,6 +46,7 @@ (^Parser [ctx] (.. Parser builder + (includeSourceSpans IncludeSourceSpans/BLOCKS_AND_INLINES) (extensions [(extensions/create ctx) (AutolinkExtension/create) (TaskListItemsExtension/create) @@ -71,75 +72,120 @@ `(binding [*in-tight-list?* (in-tight-list? ~node)] ~@body)) +(defn node->loc [^Node node] + (when-let [spans (.getSourceSpans node)] + (when-not (.isEmpty spans) + (let [^SourceSpan first-span (first spans) + ^SourceSpan last-span (last spans)] + {:input-index (.getInputIndex first-span) + :length (apply + (map #(.getLength ^Node %) spans)) + :line (.getLineIndex first-span) + :column (.getColumnIndex first-span) + :end-line (.getLineIndex last-span) + :end-column (+ (.getColumnIndex last-span) (.getLength last-span))})))) + ;; multi stuff (defmulti open-node (fn [_ctx node] (type node))) + (defmulti close-node (fn [_ctx node] (type node))) (defmethod close-node :default [ctx _node] (u/update-current-loc ctx z/up)) -(defmethod open-node Document [ctx _node] ctx) +(defmethod open-node Document [ctx _node] + ctx) + +#_(bean n) + (defmethod close-node Document [ctx _node] ctx) -(defmethod open-node Paragraph [ctx _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type (paragraph-type)})))) +(defmethod open-node Paragraph [ctx node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type (paragraph-type)} + (node->loc node)))))) -(defmethod open-node BlockQuote [ctx _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :blockquote})))) +(defmethod open-node BlockQuote [ctx node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :blockquote} + (node->loc node)))))) + +#_(parse "> Dude") (defmethod open-node Heading [ctx ^Heading node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :heading - :heading-level (.getLevel node)})))) + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :heading + :heading-level (.getLevel node)} + (node->loc node)))))) + +#_(parse "# Dude") (defmethod close-node Heading [ctx ^Heading _node] (u/handle-close-heading ctx)) (defmethod open-node HtmlInline [ctx ^HtmlInline node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :html-inline - :content [{:type :text - :text (.getLiteral node)}]})))) + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :html-inline + :content [{:type :text + :text (.getLiteral node)}]} + (node->loc node)))))) + +#_(parse "Inline ") (defmethod open-node HtmlBlock [ctx ^HtmlBlock node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :html-block - :content [{:type :text - :text (.getLiteral node)}]})))) + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :html-block + :content [{:type :text + :text (.getLiteral node)}]} + (node->loc node)))))) + +#_(parse "") -(defmethod open-node BulletList [ctx ^ListBlock _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :bullet-list :content [] #_#_:tight? (.isTight node)})))) +(defmethod open-node BulletList [ctx ^ListBlock node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :bullet-list :content [] #_#_:tight? (.isTight node)} + (node->loc node)))))) + +#_(-> (parse "- Dude") :content first) (defmethod open-node OrderedList [ctx ^OrderedList node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :numbered-list - :content [] - :attrs {:start (.getStartNumber node)}})))) + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :numbered-list + :content [] + :attrs {:start (.getStartNumber node)}} + (node->loc node)))))) + +#_(-> (parse "1. Dude") :content first) -(defmethod open-node ListItem [ctx _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :list-item :content []})))) +(defmethod open-node ListItem [ctx node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :list-item :content []} + (node->loc node)))))) -(defmethod open-node Emphasis [ctx _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :em :content []})))) +#_(-> (parse "- Dude") :content first :content first) -(defmethod open-node StrongEmphasis [ctx _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :strong :content []})))) +(defmethod open-node Emphasis [ctx node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :em :content []} + (node->loc node)))))) + +(defmethod open-node StrongEmphasis [ctx node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :strong :content []} + (node->loc node)))))) (defmethod open-node Code [ctx ^Code node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :monospace - :content [{:type :text - :text (.getLiteral node)}]})))) + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :monospace + :content [{:type :text + :text (.getLiteral node)}]} + (node->loc node)))))) -(defmethod open-node Strikethrough [ctx _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :strikethrough :content []})))) +(defmethod open-node Strikethrough [ctx node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :strikethrough :content []} + (node->loc node)))))) (defmethod open-node Link [ctx ^Link node] (u/update-current-loc ctx (fn [loc] - (u/zopen-node loc {:type :link - :attrs (cond-> {:href (.getDestination node)} - (.getTitle node) - (assoc :title (.getTitle node)))})))) + (u/zopen-node loc (merge {:type :link + :attrs (cond-> {:href (.getDestination node)} + (.getTitle node) + (assoc :title (.getTitle node)))} + (node->loc node)))))) (defmethod open-node IndentedCodeBlock [ctx ^IndentedCodeBlock node] (u/update-current-loc ctx (fn [loc] - (u/zopen-node loc {:type :code - :content [{:type :text - :text (.getLiteral node)}]})))) + (u/zopen-node loc (merge {:type :code + :content [{:type :text + :text (.getLiteral node)}]} + (node->loc node)))))) (defmethod open-node FencedCodeBlock [ctx ^FencedCodeBlock node] (u/update-current-loc ctx (fn [loc] @@ -147,20 +193,26 @@ :info (.getInfo node) :content [{:type :text :text (.getLiteral node)}]} + (node->loc node) (u/parse-fence-info (.getInfo node))))))) (defmethod open-node Image [ctx ^Image node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :image - :attrs {:src (.getDestination node) :title (.getTitle node)}})))) - -(defmethod open-node TableBlock [ctx ^TableBlock _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :table})))) -(defmethod open-node TableHead [ctx ^TableHead _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :table-head})))) -(defmethod open-node TableBody [ctx ^TableBody _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :table-body})))) -(defmethod open-node TableRow [ctx ^TableRow _node] - (u/update-current-loc ctx (fn [loc] (u/zopen-node loc {:type :table-row})))) + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :image + :attrs {:src (.getDestination node) :title (.getTitle node)}} + (node->loc node)))))) + +(defmethod open-node TableBlock [ctx ^TableBlock node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :table} + (node->loc node)))))) +(defmethod open-node TableHead [ctx ^TableHead node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :table-head} + (node->loc node)))))) +(defmethod open-node TableBody [ctx ^TableBody node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :table-body} + (node->loc node)))))) +(defmethod open-node TableRow [ctx ^TableRow node] + (u/update-current-loc ctx (fn [loc] (u/zopen-node loc (merge {:type :table-row} + (node->loc node)))))) (defn alignment->keyword [enum] (condp = enum @@ -171,29 +223,34 @@ (defmethod open-node TableCell [ctx ^TableCell node] (u/update-current-loc ctx (fn [loc] (let [alignment (some-> (.getAlignment node) alignment->keyword)] - (u/zopen-node loc (cond-> {:type (if (.isHeader node) :table-header :table-data) - :content []} - alignment - (assoc :alignment alignment))))))) + (u/zopen-node loc (merge (cond-> {:type (if (.isHeader node) :table-header :table-data) + :content []} + alignment + (assoc :alignment alignment)) + (node->loc node))))))) (defmethod open-node FootnoteDefinition [ctx ^FootnoteDefinition node] (-> ctx (assoc ::root :footnotes) (u/update-current-loc (fn [loc] (-> loc - (z/append-child {:type :footnote - :label (.getLabel node) - :content []}) z/down z/rightmost))))) + (z/append-child (merge {:type :footnote + :label (.getLabel node) + :content []} + (node->loc node))) + z/down z/rightmost))))) (defmethod close-node FootnoteDefinition [ctx ^FootnoteDefinition _node] (-> ctx (u/update-current-loc z/up) (assoc ::root :doc))) -(defmethod open-node InlineFootnote [{:as ctx ::keys [label->footnote-ref]} ^InlineFootnote _node] - (let [label (str "inline-note-" (count label->footnote-ref)) - footnote-ref {:type :footnote-ref - :inline? true - :ref (count label->footnote-ref) - :label label}] +(defmethod open-node InlineFootnote [{:as ctx ::keys [label->footnote-ref]} ^InlineFootnote node] + (let [loc (node->loc node) + label (str "inline-note-" (count label->footnote-ref)) + footnote-ref (merge {:type :footnote-ref + :inline? true + :ref (count label->footnote-ref) + :label label} + loc)] (-> ctx (u/update-current-loc z/append-child footnote-ref) (update ::label->footnote-ref assoc label footnote-ref) @@ -218,6 +275,8 @@ (.setAccessible meth true) meth)) +;; TODO: add locs to the below nodes + (defn node->data [{:as ctx-in :keys [footnotes]} ^Node node] (assert (:type ctx-in) ":type must be set on initial doc") (assert (:content ctx-in) ":content must be set on initial doc") @@ -235,21 +294,28 @@ LinkReferenceDefinition :ignore ;;Text (swap! !ctx u/update-current z/append-child {:type :text :text (.getLiteral ^Text node)}) Text (swap! !ctx u/handle-text-token (.getLiteral ^Text node)) - ThematicBreak (swap! !ctx u/update-current-loc z/append-child {:type :ruler}) - SoftLineBreak (swap! !ctx u/update-current-loc z/append-child {:type :softbreak}) - HardLineBreak (swap! !ctx u/update-current-loc z/append-child {:type :hardbreak}) + ThematicBreak (swap! !ctx u/update-current-loc z/append-child (merge {:type :ruler} + (node->loc node))) + SoftLineBreak (swap! !ctx u/update-current-loc z/append-child (merge {:type :softbreak} + (node->loc node))) + HardLineBreak (swap! !ctx u/update-current-loc z/append-child (merge {:type :hardbreak} + (node->loc node))) TaskListItemMarker (swap! !ctx u/update-current-loc handle-todo-list node) nextjournal.markdown.impl.types.CustomNode (case (t/nodeType node) - :block-formula (swap! !ctx u/update-current-loc z/append-child {:type :block-formula :text (t/getLiteral node)}) - :inline-formula (swap! !ctx u/update-current-loc z/append-child {:type :formula :text (t/getLiteral node)}) - :toc (swap! !ctx u/update-current-loc z/append-child {:type :toc})) + :block-formula (swap! !ctx u/update-current-loc z/append-child (merge {:type :block-formula :text (t/getLiteral node)} + (node->loc node))) + :inline-formula (swap! !ctx u/update-current-loc z/append-child (merge {:type :formula :text (t/getLiteral node)} + (node->loc node))) + :toc (swap! !ctx u/update-current-loc z/append-child (merge {:type :toc} + (node->loc node)))) FootnoteReference (swap! !ctx (fn [{:as ctx ::keys [label->footnote-ref]}] (let [label (.getLabel ^FootnoteReference node) footnote-ref (or (get label->footnote-ref label) - {:type :footnote-ref - :ref (count label->footnote-ref) - :label label})] + (merge {:type :footnote-ref + :ref (count label->footnote-ref) + :label label} + (node->loc node)))] (-> ctx (u/update-current-loc z/append-child footnote-ref) (update ::label->footnote-ref assoc label footnote-ref))))) @@ -299,6 +365,11 @@ (parse "some text^[and a note]") + (parse " +``` +dude +```") + (parse "**Threshold 1: (~\\$125)** - \\$240K/quarter") (parse "Formula: $1 + 2 + 3$") (parse "Money: $1 + $2")