-
-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
159 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,110 @@ | ||
;; 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 [{:keys [string-value] :as node}] | ||
[:span.clj-unknown {:data-clj-node (str (type node))} | ||
string-value]) | ||
|
||
(defmulti token-form (comp type :value)) | ||
|
||
(defmethod token-form (type true) [{:keys [string-value]}] | ||
[:code.clj__boolean {:class (styles/clj-boolean)} | ||
string-value]) | ||
|
||
(defmethod token-form (type 0) [{:keys [string-value]}] | ||
[:code.clj__number {:class (styles/clj-number)} | ||
string-value]) | ||
|
||
(defmethod token-form (type nil) [{:keys [string-value]}] | ||
[:code.clj__nil {:class (styles/clj-nil)} | ||
string-value]) | ||
|
||
(defmethod token-form :default [{:keys [string-value]}] | ||
[:span.clj__token string-value]) | ||
|
||
(defmethod form TokenNode [node] | ||
[token-form node]) | ||
|
||
(defmethod form CommaNode [_] | ||
[:span.clj__comma ","]) | ||
|
||
(defmulti seq-form :tag) | ||
|
||
(defmethod seq-form :default [_] | ||
[:code.clj__unknown]) | ||
|
||
(defmethod seq-form :list [{:keys [children] :as node}] | ||
(into [:code.seq {:class [(styles/clj-seq) | ||
(selected-style node)]}] | ||
(concat ["("] children [")"]))) | ||
|
||
(defmethod seq-form :vector [{:keys [children] :as node}] | ||
(into [:code.clj__seq {:class [(selected-style node)]}] | ||
(concat ["["] children ["]"]))) | ||
|
||
(defmethod seq-form :map [{:keys [children] :as node}] | ||
(into [:code.clj__map {:class [(selected-style node)]}] | ||
(concat ["{"] children ["}"]))) | ||
|
||
(defmethod seq-form :set [{:keys [children] :as node}] | ||
(into [:code.seq {:class [(styles/clj-seq) | ||
(selected-style node)]}] | ||
(concat ["#{"] children ["}"]))) | ||
|
||
(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 [{:keys [whitespace]}] | ||
[:code.clj__whitespace | ||
whitespace]) | ||
|
||
(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))) |