data_helper.cljs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. (ns frontend.modules.shortcut.data-helper
  2. (:require [borkdude.rewrite-edn :as rewrite]
  3. [clojure.string :as str]
  4. [clojure.set :refer [rename-keys]]
  5. [frontend.config :as config]
  6. [frontend.db :as db]
  7. [frontend.handler.file :as file]
  8. [frontend.modules.shortcut.config :as shortcut-config]
  9. [frontend.state :as state]
  10. [frontend.util :as util]
  11. [lambdaisland.glogi :as log]
  12. [frontend.handler.repo-config :as repo-config-handler]
  13. [frontend.handler.config :as config-handler])
  14. (:import [goog.ui KeyboardShortcutHandler]))
  15. (defn get-bindings
  16. []
  17. (->> (vals @shortcut-config/config)
  18. (into {})
  19. (map (fn [[k {:keys [binding]}]]
  20. {k binding}))
  21. (into {})))
  22. (defn- mod-key [shortcut]
  23. (str/replace shortcut #"(?i)mod"
  24. (if util/mac? "meta" "ctrl")))
  25. (defn shortcut-binding
  26. [id]
  27. (let [shortcut (get (state/shortcuts) id
  28. (get (get-bindings) id))]
  29. (cond
  30. (nil? shortcut)
  31. (log/warn :shortcut/binding-not-found {:id id})
  32. (false? shortcut)
  33. (do
  34. (log/debug :shortcut/disabled {:id id})
  35. false)
  36. :else
  37. (->>
  38. (if (string? shortcut)
  39. [shortcut]
  40. shortcut)
  41. (mapv mod-key)))))
  42. (defn normalize-user-keyname
  43. [k]
  44. (let [keynames {";" "semicolon"
  45. "=" "equals"
  46. "-" "dash"
  47. "[" "open-square-bracket"
  48. "]" "close-square-bracket"
  49. "'" "single-quote"}]
  50. (some-> k
  51. (util/safe-lower-case)
  52. (str/replace #"[;=-\[\]']" (fn [s]
  53. (get keynames s))))))
  54. ;; returns a vector to preserve order
  55. (defn binding-by-category [name]
  56. (let [dict (->> (vals @shortcut-config/config)
  57. (apply merge)
  58. (map (fn [[k _]]
  59. {k {:binding (shortcut-binding k)}}))
  60. (into {}))]
  61. (->> (shortcut-config/category name)
  62. (mapv (fn [k] [k (k dict)])))))
  63. (defn shortcut-map
  64. ([handler-id]
  65. (shortcut-map handler-id nil))
  66. ([handler-id state]
  67. (let [raw (get @shortcut-config/config handler-id)
  68. handler-m (->> raw
  69. (map (fn [[k {:keys [fn]}]]
  70. {k fn}))
  71. (into {}))
  72. before (-> raw meta :before)]
  73. (cond->> handler-m
  74. state (reduce-kv (fn [r k handle-fn]
  75. (assoc r k (partial handle-fn state)))
  76. {})
  77. before (reduce-kv (fn [r k v]
  78. (assoc r k (before v)))
  79. {})))))
  80. (defn decorate-namespace [k]
  81. (let [n (name k)
  82. ns (namespace k)]
  83. (keyword (str "command." ns) n)))
  84. (defn decorate-binding [binding]
  85. (-> (if (string? binding) binding (str/join "+" binding))
  86. (str/replace "mod" (if util/mac? "cmd" "ctrl"))
  87. (str/replace "alt" (if util/mac? "opt" "alt"))
  88. (str/replace "shift+/" "?")
  89. (str/replace "left" "←")
  90. (str/replace "right" "→")
  91. (str/replace "open-square-bracket" "[")
  92. (str/replace "close-square-bracket" "]")
  93. (str/lower-case)))
  94. ;; if multiple bindings, gen seq for first binding only for now
  95. (defn gen-shortcut-seq [id]
  96. (let [bindings (shortcut-binding id)]
  97. (if (false? bindings)
  98. []
  99. (-> bindings
  100. first
  101. (str/split #" |\+")))))
  102. (defn binding-for-display [k binding]
  103. (let [tmp (cond
  104. (false? binding)
  105. (cond
  106. (and util/mac? (= k :editor/kill-line-after)) "system default: ctrl+k"
  107. (and util/mac? (= k :editor/beginning-of-block)) "system default: ctrl+a"
  108. (and util/mac? (= k :editor/end-of-block)) "system default: ctrl+e"
  109. (and util/mac? (= k :editor/backward-kill-word)) "system default: opt+delete"
  110. :else "disabled")
  111. (string? binding)
  112. (decorate-binding binding)
  113. :else
  114. (->> binding
  115. (map decorate-binding)
  116. (str/join " | ")))]
  117. ;; Display "cmd" rather than "meta" to the user to describe the Mac
  118. ;; mod key, because that's what the Mac keyboards actually say.
  119. (str/replace tmp "meta" "cmd")))
  120. ;; Given the displayed binding, prepare it to be put back into config.edn
  121. (defn binding-for-storage [binding]
  122. (str/replace binding "cmd" "meta"))
  123. (defn remove-shortcut [k]
  124. (let [repo (state/get-current-repo)
  125. path (config/get-repo-config-path)]
  126. (when-let [content (db/get-file path)]
  127. (let [result (config-handler/parse-repo-config content)
  128. new-result (rewrite/update
  129. result
  130. :shortcuts
  131. #(dissoc (rewrite/sexpr %) k))
  132. new-content (str new-result)]
  133. (repo-config-handler/set-repo-config-state! repo new-content)
  134. (file/set-file-content! repo path new-content)))))
  135. (defn get-group
  136. "Given shortcut key, return handler group
  137. eg: :editor/new-line -> :shortcut.handler/block-editing-only"
  138. [k]
  139. (->> @shortcut-config/config
  140. (filter (fn [[_ v]] (contains? v k)))
  141. (map key)
  142. (first)))
  143. (defn potential-conflict? [k]
  144. (if-not (shortcut-binding k)
  145. false
  146. (let [handler-id (get-group k)
  147. shortcut-m (shortcut-map handler-id)
  148. parse-shortcut #(try
  149. (KeyboardShortcutHandler/parseStringShortcut %)
  150. (catch :default e
  151. (js/console.error "[shortcut/parse-error]" (str % " - " (.-message e)))))
  152. bindings (->> (shortcut-binding k)
  153. (map mod-key)
  154. (map parse-shortcut)
  155. (map js->clj))
  156. rest-bindings (->> (map key shortcut-m)
  157. (remove #{k})
  158. (map shortcut-binding)
  159. (filter vector?)
  160. (mapcat identity)
  161. (map mod-key)
  162. (map parse-shortcut)
  163. (map js->clj))]
  164. (some? (some (fn [b] (some #{b} rest-bindings)) bindings)))))
  165. (defn shortcut-data-by-id [id]
  166. (let [binding (shortcut-binding id)
  167. data (->> (vals @shortcut-config/config)
  168. (into {})
  169. id)]
  170. (assoc
  171. data
  172. :binding
  173. (binding-for-display id binding))))
  174. (defn shortcuts->commands [handler-id]
  175. (let [m (get @shortcut-config/config handler-id)]
  176. (->> m
  177. (map (fn [[id _]] (-> (shortcut-data-by-id id)
  178. (assoc :id id :handler-id handler-id)
  179. (rename-keys {:binding :shortcut
  180. :fn :action})))))))