Klimi's new dotfiles with stow.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

357 рядки
15 KiB

4 роки тому
  1. ;;; cider-browse-spec.el --- CIDER spec browser
  2. ;; Copyright © 2017 Juan Monetta, Bozhidar Batsov and CIDER contributors
  3. ;; Author: Juan Monetta <jpmonettas@gmail.com>
  4. ;; This program is free software: you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;; You should have received a copy of the GNU General Public License
  13. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. ;; This file is not part of GNU Emacs.
  15. ;;; Commentary:
  16. ;; M-x cider-browse-spec
  17. ;;
  18. ;; Display a spec description you can browse.
  19. ;; Pressing <enter> over a sub spec will take you to the description of that sub spec.
  20. ;; Pressing ^ takes you to the list of all specs.
  21. ;; M-x cider-browse-spec-all
  22. ;;
  23. ;; Explore clojure.spec registry by browsing a list of all specs.
  24. ;; Pressing <enter> over a spec display the spec description you can browse.
  25. ;;; Code:
  26. (require 'cider-client)
  27. (require 'cider-compat)
  28. (require 'cider-util)
  29. (require 'cl-lib)
  30. (require 'nrepl-dict)
  31. (require 'seq)
  32. (require 'subr-x)
  33. (require 'help-mode)
  34. ;; The buffer names used by the spec browser
  35. (defconst cider-browse-spec-buffer "*cider-spec-browser*")
  36. (defconst cider-browse-spec-example-buffer "*cider-spec-example*")
  37. ;; Mode Definition
  38. (defvar cider-browse-spec-mode-map
  39. (let ((map (make-sparse-keymap)))
  40. (set-keymap-parent map (make-composed-keymap button-buffer-map
  41. cider-popup-buffer-mode-map))
  42. (define-key map (kbd "RET") #'cider-browse-spec--browse-at)
  43. (define-key map "n" #'forward-button)
  44. (define-key map "p" #'backward-button)
  45. map)
  46. "Keymap for `cider-browse-spec-mode'.")
  47. (define-derived-mode cider-browse-spec-mode special-mode "Specs"
  48. "Major mode for browsing Clojure specs.
  49. \\{cider-browse-spec-mode-map}"
  50. (setq-local electric-indent-chars nil)
  51. (setq-local sesman-system 'CIDER)
  52. (when cider-special-mode-truncate-lines
  53. (setq-local truncate-lines t)))
  54. (defvar cider-browse-spec--current-spec nil)
  55. (defvar cider-browse-spec-view-mode-map
  56. (let ((map (make-sparse-keymap)))
  57. (set-keymap-parent map help-mode-map)
  58. (define-key map (kbd "RET") #'cider-browse-spec--browse-at)
  59. (define-key map "^" #'cider-browse-spec-all)
  60. (define-key map "e" #'cider-browse-spec--print-curr-spec-example)
  61. (define-key map "n" #'forward-button)
  62. (define-key map "p" #'backward-button)
  63. map)
  64. "Keymap for `cider-browse-spec-view-mode'.")
  65. (define-derived-mode cider-browse-spec-view-mode help-mode "Spec"
  66. "Major mode for displaying CIDER spec.
  67. \\{cider-browse-spec-view-mode-map}"
  68. (setq-local cider-browse-spec--current-spec nil)
  69. (setq-local electric-indent-chars nil)
  70. (setq-local sesman-system 'CIDER)
  71. (when cider-special-mode-truncate-lines
  72. (setq-local truncate-lines t)))
  73. (defvar cider-browse-spec-example-mode-map
  74. (let ((map (make-sparse-keymap)))
  75. (set-keymap-parent map cider-popup-buffer-mode-map)
  76. (define-key map "^" #'cider-browse-spec-all)
  77. (define-key map "e" #'cider-browse-spec--print-curr-spec-example)
  78. (define-key map "g" #'revert-buffer)
  79. map)
  80. "Keymap for `cider-browse-spec-example-mode'.")
  81. (define-derived-mode cider-browse-spec-example-mode special-mode "Example"
  82. "Major mode for Clojure spec examples.
  83. \\{cider-browse-spec-example-mode-map}"
  84. (setq-local electric-indent-chars nil)
  85. (setq-local revert-buffer-function #'cider-browse-spec--example-revert-buffer-function)
  86. (setq-local sesman-system 'CIDER)
  87. (when cider-special-mode-truncate-lines
  88. (setq-local truncate-lines t)))
  89. ;; Non interactive functions
  90. (define-button-type 'cider-browse-spec--spec
  91. 'action #'cider-browse-spec--browse-at
  92. 'face nil
  93. 'follow-link t
  94. 'help-echo "View spec")
  95. (defun cider-browse-spec--draw-list-buffer (buffer title specs)
  96. "Reset contents of BUFFER.
  97. Display TITLE at the top and SPECS are indented underneath."
  98. (with-current-buffer buffer
  99. (cider-browse-spec-mode)
  100. (let ((inhibit-read-only t))
  101. (erase-buffer)
  102. (goto-char (point-max))
  103. (insert (cider-propertize title 'emph) "\n")
  104. (dolist (spec-name specs)
  105. (insert (propertize " " 'spec-name spec-name))
  106. (thread-first (cider-font-lock-as-clojure spec-name)
  107. (insert-text-button 'type 'cider-browse-spec--spec)
  108. (button-put 'spec-name spec-name))
  109. (insert (propertize "\n" 'spec-name spec-name)))
  110. (goto-char (point-min)))))
  111. (defun cider--qualified-keyword-p (str)
  112. "Return non nil if STR is a namespaced keyword."
  113. (string-match-p "^:.+/.+$" str))
  114. (defun cider--spec-fn-p (value fn-name)
  115. "Return non nil if VALUE is clojure.spec.[alpha]/FN-NAME."
  116. (string-match-p (concat "^\\(clojure.spec\\|clojure.spec.alpha\\)/" fn-name "$") value))
  117. (defun cider-browse-spec--pprint (form)
  118. "Given a spec FORM builds a multi line string with a pretty render of that FORM."
  119. (cond ((stringp form)
  120. (if (cider--qualified-keyword-p form)
  121. (with-temp-buffer
  122. (thread-first form
  123. (insert-text-button 'type 'cider-browse-spec--spec)
  124. (button-put 'spec-name form))
  125. (buffer-string))
  126. ;; to make it easier to read replace all clojure.spec ns with s/
  127. ;; and remove all clojure.core ns
  128. (thread-last form
  129. (replace-regexp-in-string "^\\(clojure.spec\\|clojure.spec.alpha\\)/" "s/")
  130. (replace-regexp-in-string "^\\(clojure.core\\)/" ""))))
  131. ((and (listp form) (stringp (cl-first form)))
  132. (let ((form-tag (cl-first form)))
  133. (cond
  134. ;; prettier fns #()
  135. ((string-equal form-tag "clojure.core/fn")
  136. (if (equal (cl-second form) '("%"))
  137. (format "#%s" (cl-reduce #'concat (mapcar #'cider-browse-spec--pprint (cl-rest (cl-rest form)))))
  138. (format "(fn [%%] %s)" (cl-reduce #'concat (mapcar #'cider-browse-spec--pprint (cl-rest (cl-rest form)))))))
  139. ;; prettier (s/and )
  140. ((cider--spec-fn-p form-tag "and")
  141. (format "(s/and\n%s)" (string-join (thread-last (cl-rest form)
  142. (mapcar #'cider-browse-spec--pprint)
  143. (mapcar (lambda (x) (format "%s" x))))
  144. "\n")))
  145. ;; prettier (s/or )
  146. ((cider--spec-fn-p form-tag "or")
  147. (let ((name-spec-pair (seq-partition (cl-rest form) 2)))
  148. (format "(s/or\n%s)" (string-join
  149. (thread-last name-spec-pair
  150. (mapcar (lambda (s) (format "%s %s" (cl-first s) (cider-browse-spec--pprint (cl-second s))))))
  151. "\n"))))
  152. ;; prettier (s/merge )
  153. ((cider--spec-fn-p form-tag "merge")
  154. (format "(s/merge\n%s)" (string-join (thread-last (cl-rest form)
  155. (mapcar #'cider-browse-spec--pprint)
  156. (mapcar (lambda (x) (format "%s" x))))
  157. "\n")))
  158. ;; prettier (s/keys )
  159. ((cider--spec-fn-p form-tag "keys")
  160. (let ((keys-args (seq-partition (cl-rest form) 2)))
  161. (format "(s/keys%s)" (thread-last
  162. keys-args
  163. (mapcar (lambda (s)
  164. (let ((key-type (cl-first s))
  165. (specs-vec (cl-second s)))
  166. (concat "\n" key-type
  167. " ["
  168. (string-join (thread-last specs-vec
  169. (mapcar #'cider-browse-spec--pprint)
  170. (mapcar (lambda (x) (format "%s" x))))
  171. "\n")
  172. "]"))))
  173. (cl-reduce #'concat)))))
  174. ;; prettier (s/multi-spec)
  175. ((cider--spec-fn-p form-tag "multi-spec")
  176. (let ((multi-method (cl-second form))
  177. (retag (cl-third form))
  178. (sub-specs (cl-rest (cl-rest (cl-rest form)))))
  179. (format "(s/multi-spec %s %s\n%s)"
  180. multi-method
  181. retag
  182. (string-join
  183. (thread-last sub-specs
  184. (mapcar (lambda (s)
  185. (concat "\n\n" (cl-first s) " " (cider-browse-spec--pprint (cl-second s))))))
  186. "\n"))))
  187. ;; prettier (s/cat )
  188. ((cider--spec-fn-p form-tag "cat")
  189. (let ((name-spec-pairs (seq-partition (cl-rest form) 2)))
  190. (format "(s/cat %s)"
  191. (thread-last name-spec-pairs
  192. (mapcar (lambda (s)
  193. (concat "\n" (cl-first s) " " (cider-browse-spec--pprint (cl-second s)))))
  194. (cl-reduce #'concat)))))
  195. ;; prettier (s/alt )
  196. ((cider--spec-fn-p form-tag "alt")
  197. (let ((name-spec-pairs (seq-partition (cl-rest form) 2)))
  198. (format "(s/alt %s)"
  199. (thread-last name-spec-pairs
  200. (mapcar (lambda (s)
  201. (concat "\n" (cl-first s) " " (cider-browse-spec--pprint (cl-second s)))))
  202. (cl-reduce #'concat)))))
  203. ;; prettier (s/fspec )
  204. ((cider--spec-fn-p form-tag "fspec")
  205. (thread-last (seq-partition (cl-rest form) 2)
  206. (cl-remove-if (lambda (s) (and (stringp (cl-second s))
  207. (string-empty-p (cl-second s)))))
  208. (mapcar (lambda (s)
  209. (format "\n%-11s: %s" (pcase (cl-first s)
  210. (":args" "arguments")
  211. (":ret" "returns")
  212. (":fn" "invariants"))
  213. (cider-browse-spec--pprint (cl-second s)))))
  214. (cl-reduce #'concat)
  215. (format "%s")))
  216. ;; every other with no special management
  217. (t (format "(%s %s)"
  218. (cider-browse-spec--pprint form-tag)
  219. (string-join (mapcar #'cider-browse-spec--pprint (cl-rest form)) " "))))))
  220. (t (format "%s" form))))
  221. (defun cider-browse-spec--pprint-indented (spec-form)
  222. "Indent (pretty-print) and font-lock SPEC-FORM.
  223. Return the result as a string."
  224. (with-temp-buffer
  225. (clojure-mode)
  226. (insert (cider-browse-spec--pprint spec-form))
  227. (indent-region (point-min) (point-max))
  228. (cider--font-lock-ensure)
  229. (buffer-string)))
  230. (defun cider-browse-spec--draw-spec-buffer (buffer spec spec-form)
  231. "Reset contents of BUFFER and draws everything needed to browse the SPEC-FORM.
  232. Display SPEC as a title and uses `cider-browse-spec--pprint' to display
  233. a more user friendly representation of SPEC-FORM."
  234. (with-current-buffer buffer
  235. (let ((inhibit-read-only t))
  236. (cider--help-setup-xref (list #'cider-browse-spec spec) nil buffer)
  237. (goto-char (point-max))
  238. (insert (cider-font-lock-as-clojure spec) "\n\n")
  239. (insert (cider-browse-spec--pprint-indented spec-form))
  240. (cider--make-back-forward-xrefs)
  241. (current-buffer))))
  242. (defun cider-browse-spec--browse (spec)
  243. "Browse SPEC."
  244. (cider-ensure-connected)
  245. (cider-ensure-op-supported "spec-form")
  246. (with-current-buffer (cider-popup-buffer cider-browse-spec-buffer 'select #'cider-browse-spec-view-mode 'ancillary)
  247. (setq-local cider-browse-spec--current-spec spec)
  248. (cider-browse-spec--draw-spec-buffer (current-buffer)
  249. spec
  250. (cider-sync-request:spec-form spec))
  251. (goto-char (point-min))
  252. (current-buffer)))
  253. (defun cider-browse-spec--browse-at (&optional pos)
  254. "View the definition of a spec.
  255. Optional argument POS is the position of a spec, defaulting to point. POS
  256. may also be a button, so this function can be used a the button's `action'
  257. property."
  258. (interactive)
  259. (let ((pos (or pos (point))))
  260. (when-let* ((spec (button-get pos 'spec-name)))
  261. (cider-browse-spec--browse spec))))
  262. ;; Interactive Functions
  263. (defun cider-browse-spec--print-curr-spec-example ()
  264. "Generate and print an example of the current spec."
  265. (interactive)
  266. (cider-ensure-connected)
  267. (cider-ensure-op-supported "spec-example")
  268. (if-let* ((spec cider-browse-spec--current-spec))
  269. (if-let* ((example (cider-sync-request:spec-example spec)))
  270. (with-current-buffer (cider-popup-buffer cider-browse-spec-example-buffer 'select #'cider-browse-spec-example-mode 'ancillary)
  271. (setq-local cider-browse-spec--current-spec spec)
  272. (let ((inhibit-read-only t))
  273. (insert "Example of " (cider-font-lock-as-clojure spec))
  274. (insert "\n\n")
  275. (insert (cider-font-lock-as-clojure example))
  276. (goto-char (point-min))))
  277. (error (format "No example for spec %s" spec)))
  278. (error "No current spec")))
  279. (defun cider-browse-spec--example-revert-buffer-function (&rest _)
  280. "`revert-buffer' function for `cider-browse-spec-example-mode'.
  281. Generates a new example for the current spec."
  282. (cider-browse-spec--print-curr-spec-example))
  283. ;;;###autoload
  284. (defun cider-browse-spec (spec)
  285. "Browse SPEC definition."
  286. (interactive (list (completing-read "Browse spec: "
  287. (cider-sync-request:spec-list)
  288. nil nil
  289. (cider-symbol-at-point))))
  290. (cider-browse-spec--browse spec))
  291. (defun cider-browse-spec-regex (regex)
  292. "Open the list of specs that matches REGEX in a popup buffer.
  293. Displays all specs when REGEX is nil."
  294. (cider-ensure-connected)
  295. (cider-ensure-op-supported "spec-list")
  296. (let ((filter-regex (or regex "")))
  297. (with-current-buffer (cider-popup-buffer cider-browse-spec-buffer 'select nil 'ancillary)
  298. (let ((specs (cider-sync-request:spec-list filter-regex)))
  299. (cider-browse-spec--draw-list-buffer (current-buffer)
  300. (if (string-empty-p filter-regex)
  301. "All specs in registry"
  302. (format "All specs matching regex `%s' in registry" filter-regex))
  303. specs)))))
  304. ;;;###autoload
  305. (defun cider-browse-spec-all (&optional arg)
  306. "Open list of specs in a popup buffer.
  307. With a prefix argument ARG, prompts for a regexp to filter specs.
  308. No filter applied if the regexp is the empty string."
  309. (interactive "P")
  310. (cider-browse-spec-regex (if arg (read-string "Filter regex: ") "")))
  311. (provide 'cider-browse-spec)
  312. ;;; cider-browse-spec.el ends here