Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First cut of diff-text viewer #238

Merged
merged 4 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dev/tasks/docs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
::v/diff
{:file "portal/ui/viewer/diff.cljs"
:examples [(vary-meta d/diff-data dissoc ::v/default)]}
::v/diff-text
{:file "portal/ui/viewer/diff_text.cljs"
:examples [d/diff-text-data]}
::v/tree
{:examples [(vary-meta d/hiccup dissoc ::v/default)]}
::v/code
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.16",
"anser": "^2.1.1",
"diff": "^5.2.0",
"highlight.js": "^11.3.1",
"marked": "^4.1.0",
"papaparse": "^5.3.1",
Expand Down
1 change: 1 addition & 0 deletions resources/viewers.edn
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
{:name :portal.viewer/diff,
:doc
"Diff a collection of values successively starting with the first two."}
{:name :portal.viewer/diff-text, :doc "Diff two strings."}
{:name :portal.viewer/markdown,
:doc "Parse string as markdown and view as html."}
{:name :portal.viewer/hiccup,
Expand Down
9 changes: 8 additions & 1 deletion src/examples/data.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
(:require #?(:clj [clojure.java.io :as io])
#?(:org.babashka/nbb [clojure.core]
:default [examples.hacker-news :as hn])
[clojure.pprint :as pp]
[examples.macros :refer [read-file]]
[portal.colors :as c]
[portal.viewer :as v])
#?(:clj (:import [java.io File ByteArrayOutputStream]
#?(:clj (:import [java.io ByteArrayOutputStream File]
[java.net URI]
[java.util Date]
[java.util UUID])
Expand Down Expand Up @@ -242,6 +243,11 @@
::vector [::a ::added ::b]
::different-value ::new-key}]))

(def diff-text-data
(v/diff-text
[(with-out-str (pp/pprint (first diff-data)))
(with-out-str (pp/pprint (second diff-data)))]))

(def string-data
(v/for
{::json "{\"hello\": 123}"
Expand Down Expand Up @@ -1121,6 +1127,7 @@
::spec-data spec-data
::table-data table-data
::diff diff-data
::diff-text diff-text-data
::basic-data basic-data
::themes c/themes
::clojure-data clojure-data
Expand Down
2 changes: 2 additions & 0 deletions src/portal/ui/app.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
[portal.ui.viewer.date-time :as date-time]
[portal.ui.viewer.deref :as deref]
[portal.ui.viewer.diff :as diff]
[portal.ui.viewer.diff-text :as diff-text]
[portal.ui.viewer.duration :as duration]
[portal.ui.viewer.edn :as edn]
[portal.ui.viewer.exception :as ex]
Expand Down Expand Up @@ -489,6 +490,7 @@
csv/viewer
html/viewer
diff/viewer
diff-text/viewer
md/viewer
hiccup/viewer
date-time/viewer
Expand Down
28 changes: 9 additions & 19 deletions src/portal/ui/commands.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -924,34 +924,24 @@
(register! #'nav {:name 'clojure.datafy/nav
:predicate (comp :collection state/get-selected-context)})

(defn- get-style []
(some-> js/document
(.getElementsByTagName "html")
(aget 0)
(.getAttribute "style")
not-empty))

(defn- get-vs-code-css-vars []
(when-let [style (get-style)]
(persistent!
(reduce
(fn [vars rule]
(if-let [[attr value] (str/split rule #"\s*:\s*")]
(assoc! vars attr value)
vars))
(transient {})
(str/split style #"\s*;\s*")))))

(defn- vs-code-vars
"List all available css variable provided by vs-code."
[state]
(state/dispatch!
state
state/history-push
{:portal/value (get-vs-code-css-vars)}))
{:portal/value (theme/get-vs-code-css-vars)}))

(register! #'vs-code-vars {:predicate theme/is-vs-code?})

(defn- vs-code-copy-theme
[_]
(-> (theme/get-vs-code-css-vars)
(walk/postwalk-replace (::c/vs-code-embedded c/themes))
(copy-edn!)))

(register! #'vs-code-copy-theme {:predicate theme/is-vs-code?})

(defn copy-str
"Copy string to the clipboard."
{:shortcuts [#{"shift" "c"}]}
Expand Down
45 changes: 41 additions & 4 deletions src/portal/ui/theme.cljs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(ns portal.ui.theme
(:require ["react" :as react]
[clojure.string :as str]
[portal.colors :as c]
[portal.ui.options :as opts]
[portal.ui.react :refer [use-effect]]))
Expand All @@ -11,17 +12,38 @@
(.getPropertyValue "--vscode-font-size")
(not= "")))

(defn- get-style []
(some-> js/document
(.getElementsByTagName "html")
(aget 0)
(.getAttribute "style")
not-empty))

(defn ^:no-doc get-vs-code-css-vars []
(when-let [style (get-style)]
(persistent!
(reduce
(fn [vars rule]
(if-let [[attr value] (str/split rule #"\s*:\s*")]
(assoc! vars (str "var(" attr ")") value)
vars))
(transient {})
(str/split style #"\s*;\s*")))))

(defn- get-theme [theme-name]
(let [opts (opts/use-options)]
(let [opts (opts/use-options)
vars (get-vs-code-css-vars)]
(merge
{:font-family "monospace"
:font-size "12pt"
:string-length 100
:max-depth 2
:padding 6
:border-radius 2}
(or (get c/themes theme-name)
(get (:themes opts) theme-name)))))
(update-vals
(or (get c/themes theme-name)
(get (:themes opts) theme-name))
#(get vars % %)))))

(defn- use-theme-detector []
(let [media-query (.matchMedia js/window "(prefers-color-scheme: dark)")
Expand All @@ -34,6 +56,20 @@
(.removeListener media-query listener))))
dark-theme))

(defn- use-vscode-theme-detector []
(let [[change-id set-change-id!] (react/useState 0)]
(when (is-vs-code?)
(react/useEffect
(fn []
(let [observer (js/MutationObserver. #(set-change-id! inc))]
(.observe observer
js/document.documentElement
#js {:attributes true
:attributeFilter #js ["style"]})
#(.disconnect observer)))
#js []))
change-id))

(defn- default-theme [dark-theme]
(cond
(is-vs-code?) ::c/vs-code-embedded
Expand All @@ -57,8 +93,9 @@

(defn with-theme [theme-name & children]
(let [dark-theme (use-theme-detector)
theme-key (use-vscode-theme-detector)
theme (get-theme (or theme-name (default-theme dark-theme)))]
(into [:r> (.-Provider theme-context) #js {:value theme}] children)))
(into [:r> (.-Provider theme-context) #js {:key theme-key :value theme}] children)))

(defonce ^:no-doc order
(cycle [::c/diff-remove ::c/diff-add ::c/keyword ::c/tag ::c/number ::c/uri]))
Expand Down
121 changes: 121 additions & 0 deletions src/portal/ui/viewer/diff_text.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
(ns portal.ui.viewer.diff-text
(:require ["diff" :as df]
[clojure.spec.alpha :as s]
[clojure.string :as str]
[portal.colors :as c]
[portal.ui.icons :as icons]
[portal.ui.inspector :as ins]
[portal.ui.lazy :as l]
[portal.ui.styled :as d]
[portal.ui.theme :as theme]))

;;; :spec
(s/def ::diff-text (s/cat :a string? :b string?))
;;;

(defn- diff-text? [value]
(s/valid? ::diff-text value))

(defn- changed? [^js item]
(or (some-> item .-added)
(some-> item .-removed)))

(defn- inspect-text-diff [value]
(let [theme (theme/use-theme)
add (::c/diff-add theme)
remove (::c/diff-remove theme)
diff (df/diffLines (or (:- value) (first value))
(or (:+ value) (second value)))
opts (ins/use-options)
bg (ins/get-background)]
[:pre
{:style {:margin 0 :white-space :pre-wrap :background bg}}
[d/div
{:style {:height (:padding theme)
:padding-left (:padding theme)
:border-top [1 :solid (::c/border theme)]
:border-left [5 :solid (::c/border theme)]
:border-right [1 :solid (::c/border theme)]
:border-top-left-radius (:border-radius theme)
:border-top-right-radius (:border-radius theme)}}]
[l/lazy-seq
(map-indexed
(fn [index [before ^js item after]]
(let [added (.-added item)
removed (.-removed item)
text (.-value item)
border-color (cond
added add
removed remove
:else (::c/border theme))]
^{:key index}
[d/div
{:style
{:position :relative
:border-left [5 :solid border-color]
:border-right [1 :solid border-color]
:background (cond added (str add "22")
removed (str remove "22"))}}
(if-not (or removed added)
(if (:expanded? opts)
[ins/highlight-words text]
(let [lines (str/split-lines text)]
(if (< (count lines) 6)
[ins/highlight-words text]
[:<>
(when before
[:<>
[ins/highlight-words (str/join "\n" (take 3 lines))]
[d/div {:style {:background (::c/border theme)
:text-align :center}}
[icons/ellipsis-h]]])
(when after
[:<>
[d/div {:style {:background (::c/border theme)
:text-align :center}}
[icons/ellipsis-h]]
[ins/highlight-words (str/join "\n" (take-last 3 lines))]])])))
(cond
(changed? before)
(keep-indexed
(fn [idx ^js item]
(when (or (.-added item) (not (.-removed item)))
^{:key idx}
[d/span {:style {:background (when (.-added item)
(if added
(str add "66")
(str remove "66")))}}
[ins/highlight-words (.-value item)]]))
(df/diffChars (.-value before) (str/trimr text)))

(changed? after)
(keep-indexed
(fn [idx ^js item]
(when (or (.-added item) (not (.-removed item)))
^{:key idx}
[d/span {:style {:background (when (.-added item)
(if added
(str add "66")
(str remove "66")))}}
[ins/highlight-words (.-value item)]]))
(df/diffChars (.-value after) (str/trimr text)))

:else
(str/trimr text)))

(when (or removed added) "\n")]))
(partition 3 1 (concat [nil] diff [nil])))]
[d/div
{:style {:height (:padding theme)
:padding-left (:padding theme)
:border-bottom [1 :solid (::c/border theme)]
:border-left [5 :solid (::c/border theme)]
:border-right [1 :solid (::c/border theme)]
:border-bottom-left-radius (:border-radius theme)
:border-bottom-right-radius (:border-radius theme)}}]]))

(def viewer
{:predicate diff-text?
:component inspect-text-diff
:name :portal.viewer/diff-text
:doc "Diff two strings."})
5 changes: 5 additions & 0 deletions src/portal/viewer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@
([value] (default value ::diff))
([value opts] (default value ::diff opts)))

(defn diff-text
"Diff two strings."
([value] (default value ::diff-text))
([value opts] (default value ::diff-text opts)))

(defn prepl
"View interlacing of stdout, stderr and tap values. Useful for build output."
([value] (default value ::prepl))
Expand Down
Loading