Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[wip] Highlight code with rewrite-clj, not highlight.js
The highlight.js integration has been costly to maintain, both for day8 and for our users. See: #376 Now, we use rewrite-clj and reagent for minimalistic highlighting. This is similar to borkdude's approach: https://blog.michielborkent.nl/writing-clojure-highlighter.html rewrite-clj labels forms by their type and purpose. It provides a zipper api for expressive traversal, including line & char numbers. Now, we simply transform rewrite-clj's node tree into hiccup. We do two post-order traversals, which isn't ideal, but it works. We could consolidate the traversal with some careful refactoring. TODO: ::highlighted-form-bounds is mostly regex math which reverse-engineers the output of re-frame-debux. Now, ::highlighted? adds another layer of reverse-engineering. It seems like we could delete all this calculation, and delete zprint, if re-frame-debux could simply provide the node tree. re-frame-debux could also provide some analysis data from clj-kondo, making it possible to color locals differently from globals, for instance.
- Loading branch information
Showing
7 changed files
with
152 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
;; TODO: make this a standalone library | ||
|
||
(ns day8.re-frame-10x.tools.highlight-hiccup | ||
(:require [clojure.walk :as walk] | ||
[rewrite-clj.zip :as rz] | ||
[rewrite-clj.node.token :refer [SymbolNode TokenNode]] | ||
[rewrite-clj.node.whitespace :refer [WhitespaceNode NewlineNode CommaNode]] | ||
[rewrite-clj.node.keyword :refer [KeywordNode]] | ||
[rewrite-clj.node.stringz :refer [StringNode]] | ||
[rewrite-clj.node.seq :refer [SeqNode]] | ||
[day8.re-frame-10x.styles :as styles] | ||
[day8.re-frame-10x.inlined-deps.re-frame.v1v1v2.re-frame.core :as rf] | ||
[day8.re-frame-10x.panels.event.subs :as event.subs])) | ||
|
||
|
||
|
||
(def clj-core-macros #{'and 'binding 'case 'catch 'comment 'cond 'cond-> 'cond->> 'condp 'def | ||
'defmacro 'defn 'defn- 'defmulti 'defmethod 'defonce 'defprotocol 'deftype | ||
'do 'dotimes 'doseq 'dosync 'fn 'for 'future 'if 'if-let 'if-not 'import 'let | ||
'letfn 'locking 'loop 'ns 'or 'proxy 'quote 'recur 'set! 'struct-map 'sync 'throw | ||
'try 'when 'when-first 'when-let 'when-not 'when-some 'while}) | ||
|
||
(defn selected-style [{:keys [position]}] | ||
(when @(rf/subscribe [::event.subs/highlighted? position]) | ||
(styles/clj-highlighted))) | ||
|
||
(defmulti form type) | ||
|
||
(defmethod form :default [node] [:span.clj-unknown {:data-node (type node)} (str (type node))]) | ||
|
||
(defmulti tf2 (comp type :value)) | ||
|
||
(defmethod tf2 (type true) [{:keys [string-value]}] | ||
[:code.clj__boolean {:class (styles/clj-boolean)} | ||
string-value]) | ||
|
||
(defmethod tf2 (type 0) [{:keys [string-value]}] | ||
[:code.clj_number {:class (styles/clj-number)} | ||
string-value]) | ||
|
||
(defmethod tf2 (type nil) [{:keys [string-value]}] | ||
[:code.clj__nil {:class (styles/clj-nil)} string-value]) | ||
|
||
(defmethod tf2 :default [{:keys [string-value] :as node}] | ||
[:span.clj__token (str (keys node)) string-value]) | ||
|
||
(defmethod form TokenNode [node] | ||
[tf2 node]) | ||
|
||
(defmethod form (type []) [node] node) | ||
|
||
(defmethod form CommaNode [node] [:span.clj__comma ","]) | ||
|
||
(defmulti seq-form :tag) | ||
|
||
(defmethod seq-form :default [{:keys [tag]}] | ||
[:code.clj__unknown tag]) | ||
|
||
(defmethod seq-form :list [node] | ||
(into [:code.seq {:class [(styles/clj-seq) | ||
(selected-style node)]}] | ||
(concat [ "("] (:children node) [")"]))) | ||
|
||
(defmethod seq-form :vector [node] | ||
(into [:code.clj__seq] (concat ["["] (:children node) ["]"]))) | ||
|
||
(defmethod seq-form :map [node] | ||
(into [:code.clj__map {:class [(selected-style node)]}] | ||
(concat ["{"] (:children node) ["}"]))) | ||
|
||
(defmethod form SeqNode [node] | ||
(seq-form node)) | ||
|
||
(defmethod form SymbolNode [{:keys [value string-value] :as node}] | ||
[:code.clj__symbol {:class [(if (clj-core-macros value) | ||
(styles/clj-core-macro) | ||
(styles/clj-symbol)) | ||
(selected-style node)]} | ||
string-value]) | ||
|
||
(defmethod form WhitespaceNode [node] | ||
[:code.clj__whitespace {:style {:white-space "pre"}} | ||
(:whitespace node)]) | ||
|
||
(defmethod form NewlineNode [_] [:br]) | ||
|
||
(defmethod form KeywordNode [{:keys [k] :as node}] | ||
[:code.clj__keyword {:class [(styles/clj-keyword) | ||
(selected-style node)]} | ||
(str k)]) | ||
|
||
(defmethod form StringNode [{:keys [lines]}] | ||
[:code.clj__string {:class (styles/clj-string)} | ||
\" (apply str lines) \"]) | ||
|
||
(defn str->hiccup [s] | ||
(let [positional-ast | ||
(-> s | ||
(rz/of-string {:track-position? true}) | ||
(rz/postwalk #(rz/edit* % assoc | ||
:position (rz/position %))) | ||
rz/node)] | ||
(walk/postwalk #(cond-> % (record? %) form) positional-ast))) |