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")