Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
9.0 KiB

4 years ago
  1. ;;; cider-apropos.el --- Apropos functionality for Clojure -*- lexical-binding: t -*-
  2. ;; Copyright © 2014-2019 Jeff Valk, Bozhidar Batsov and CIDER contributors
  3. ;;
  4. ;; Author: Jeff Valk <jv@jeffvalk.com>
  5. ;; This program is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;; This file is not part of GNU Emacs.
  16. ;;; Commentary:
  17. ;; Apropos functionality for Clojure.
  18. ;;; Code:
  19. (require 'cider-doc)
  20. (require 'cider-util)
  21. (require 'subr-x)
  22. (require 'cider-compat)
  23. (require 'cider-client)
  24. (require 'cider-popup)
  25. (require 'nrepl-dict)
  26. (require 'clojure-mode)
  27. (require 'apropos)
  28. (require 'button)
  29. (defconst cider-apropos-buffer "*cider-apropos*")
  30. (defcustom cider-apropos-actions '(("display-doc" . cider-doc-lookup)
  31. ("find-def" . cider--find-var)
  32. ("lookup-on-grimoire" . cider-grimoire-lookup))
  33. "Controls the actions to be applied on the symbol found by an apropos search.
  34. The first action key in the list will be selected as default. If the list
  35. contains only one action key, the associated action function will be
  36. applied automatically. An action function can be any function that receives
  37. the symbol found by the apropos search as argument."
  38. :type '(alist :key-type string :value-type function)
  39. :group 'cider
  40. :package-version '(cider . "0.13.0"))
  41. (define-button-type 'apropos-special-form
  42. 'apropos-label "Special form"
  43. 'apropos-short-label "s"
  44. 'face 'font-lock-keyword-face
  45. 'help-echo "mouse-2, RET: Display more help on this special form"
  46. 'follow-link t
  47. 'action (lambda (button)
  48. (describe-function (button-get button 'apropos-symbol))))
  49. (defun cider-apropos-doc (button)
  50. "Display documentation for the symbol represented at BUTTON."
  51. (cider-doc-lookup (button-get button 'apropos-symbol)))
  52. (defun cider-apropos-summary (query ns docs-p include-private-p case-sensitive-p)
  53. "Return a short description for the performed apropos search.
  54. QUERY can be a regular expression list of space-separated words
  55. \(e.g take while) which will be converted to a regular expression
  56. \(like take.+while) automatically behind the scenes. The search may be
  57. limited to the namespace NS, and may optionally search doc strings
  58. \(based on DOCS-P), include private vars (based on INCLUDE-PRIVATE-P),
  59. and be case-sensitive (based on CASE-SENSITIVE-P)."
  60. (concat (if case-sensitive-p "Case-sensitive " "")
  61. (if docs-p "Documentation " "")
  62. (format "Apropos for %S" query)
  63. (if ns (format " in namespace %S" ns) "")
  64. (if include-private-p
  65. " (public and private symbols)"
  66. " (public symbols only)")))
  67. (defun cider-apropos-highlight (doc query)
  68. "Return the DOC string propertized to highlight QUERY matches."
  69. (let ((pos 0))
  70. (while (string-match query doc pos)
  71. (setq pos (match-end 0))
  72. (put-text-property (match-beginning 0)
  73. (match-end 0)
  74. 'font-lock-face apropos-match-face doc)))
  75. doc)
  76. (defun cider-apropos-result (result query docs-p)
  77. "Emit a RESULT matching QUERY into current buffer, formatted for DOCS-P."
  78. (nrepl-dbind-response result (name type doc)
  79. (let* ((label (capitalize (if (string= type "variable") "var" type)))
  80. (help (concat "Display doc for this " (downcase label))))
  81. (cider-propertize-region (list 'apropos-symbol name
  82. 'action 'cider-apropos-doc
  83. 'help-echo help)
  84. (insert-text-button name 'type 'apropos-symbol)
  85. (insert "\n ")
  86. (insert-text-button label 'type (intern (concat "apropos-" type)))
  87. (insert ": ")
  88. (let ((beg (point)))
  89. (if docs-p
  90. (insert (cider-apropos-highlight doc query) "\n")
  91. (insert doc)
  92. (fill-region beg (point))))
  93. (insert "\n")))))
  94. (declare-function cider-mode "cider-mode")
  95. (defun cider-show-apropos (summary results query docs-p)
  96. "Show SUMMARY and RESULTS for QUERY in a pop-up buffer, formatted for DOCS-P."
  97. (with-current-buffer (cider-popup-buffer cider-apropos-buffer 'select 'apropos-mode 'ancillary)
  98. (let ((inhibit-read-only t))
  99. (if (boundp 'header-line-format)
  100. (setq-local header-line-format summary)
  101. (insert summary "\n\n"))
  102. (dolist (result results)
  103. (cider-apropos-result result query docs-p))
  104. (goto-char (point-min)))))
  105. ;;;###autoload
  106. (defun cider-apropos (query &optional ns docs-p privates-p case-sensitive-p)
  107. "Show all symbols whose names match QUERY, a regular expression.
  108. QUERY can also be a list of space-separated words (e.g. take while) which
  109. will be converted to a regular expression (like take.+while) automatically
  110. behind the scenes. The search may be limited to the namespace NS, and may
  111. optionally search doc strings (based on DOCS-P), include private vars
  112. \(based on PRIVATES-P), and be case-sensitive (based on CASE-SENSITIVE-P)."
  113. (interactive
  114. (cons (read-string "Search for Clojure symbol (a regular expression): ")
  115. (when current-prefix-arg
  116. (list (let ((ns (completing-read "Namespace (default is all): " (cider-sync-request:ns-list))))
  117. (if (string= ns "") nil ns))
  118. (y-or-n-p "Search doc strings? ")
  119. (y-or-n-p "Include private symbols? ")
  120. (y-or-n-p "Case-sensitive? ")))))
  121. (cider-ensure-connected)
  122. (cider-ensure-op-supported "apropos")
  123. (if-let* ((summary (cider-apropos-summary
  124. query ns docs-p privates-p case-sensitive-p))
  125. (results (cider-sync-request:apropos query ns docs-p privates-p case-sensitive-p)))
  126. (cider-show-apropos summary results query docs-p)
  127. (message "No apropos matches for %S" query)))
  128. ;;;###autoload
  129. (defun cider-apropos-documentation ()
  130. "Shortcut for (cider-apropos <query> nil t)."
  131. (interactive)
  132. (cider-ensure-connected)
  133. (cider-ensure-op-supported "apropos")
  134. (cider-apropos (read-string "Search for Clojure documentation (a regular expression): ") nil t))
  135. (defun cider-apropos-act-on-symbol (symbol)
  136. "Apply selected action on SYMBOL."
  137. (let* ((first-action-key (car (car cider-apropos-actions)))
  138. (action-key (if (= 1 (length cider-apropos-actions))
  139. first-action-key
  140. (completing-read (format "Choose action to apply to `%s` (default %s): "
  141. symbol first-action-key)
  142. cider-apropos-actions nil nil nil nil first-action-key)))
  143. (action-fn (cdr (assoc action-key cider-apropos-actions))))
  144. (if action-fn
  145. (funcall action-fn symbol)
  146. (user-error "Unknown action `%s`" action-key))))
  147. ;;;###autoload
  148. (defun cider-apropos-select (query &optional ns docs-p privates-p case-sensitive-p)
  149. "Similar to `cider-apropos', but presents the results in a completing read.
  150. Show all symbols whose names match QUERY, a regular expression.
  151. QUERY can also be a list of space-separated words (e.g. take while) which
  152. will be converted to a regular expression (like take.+while) automatically
  153. behind the scenes. The search may be limited to the namespace NS, and may
  154. optionally search doc strings (based on DOCS-P), include private vars
  155. \(based on PRIVATES-P), and be case-sensitive (based on CASE-SENSITIVE-P)."
  156. (interactive
  157. (cons (read-string "Search for Clojure symbol (a regular expression): ")
  158. (when current-prefix-arg
  159. (list (let ((ns (completing-read "Namespace (default is all): " (cider-sync-request:ns-list))))
  160. (if (string= ns "") nil ns))
  161. (y-or-n-p "Search doc strings? ")
  162. (y-or-n-p "Include private symbols? ")
  163. (y-or-n-p "Case-sensitive? ")))))
  164. (cider-ensure-connected)
  165. (cider-ensure-op-supported "apropos")
  166. (if-let* ((summary (cider-apropos-summary
  167. query ns docs-p privates-p case-sensitive-p))
  168. (results (mapcar (lambda (r) (nrepl-dict-get r "name"))
  169. (cider-sync-request:apropos query ns docs-p privates-p case-sensitive-p))))
  170. (cider-apropos-act-on-symbol (completing-read (concat summary ": ") results))
  171. (message "No apropos matches for %S" query)))
  172. ;;;###autoload
  173. (defun cider-apropos-documentation-select ()
  174. "Shortcut for (cider-apropos-select <query> nil t)."
  175. (interactive)
  176. (cider-ensure-connected)
  177. (cider-ensure-op-supported "apropos")
  178. (cider-apropos-select (read-string "Search for Clojure documentation (a regular expression): ") nil t))
  179. (provide 'cider-apropos)
  180. ;;; cider-apropos.el ends here