;;; cider-apropos.el --- Apropos functionality for Clojure -*- lexical-binding: t -*- ;; Copyright © 2014-2016 Jeff Valk, Bozhidar Batsov and CIDER contributors ;; ;; Author: Jeff Valk ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;; This file is not part of GNU Emacs. ;;; Commentary: ;; Apropos functionality for Clojure. ;;; Code: (require 'cider-doc) (require 'cider-util) (require 'cider-compat) (require 'cider-client) (require 'cider-popup) (require 'nrepl-client) (require 'clojure-mode) (require 'apropos) (require 'button) (defconst cider-apropos-buffer "*cider-apropos*") (push cider-apropos-buffer cider-ancillary-buffers) (defun cider-apropos-doc (button) "Display documentation for the symbol represented at BUTTON." (cider-doc-lookup (button-get button 'apropos-symbol))) (defun cider-apropos-summary (query ns docs-p include-private-p case-sensitive-p) "Return a short description for the performed apropos search. QUERY can be a regular expression list of space-separated words \(e.g take while) which will be converted to a regular expression \(like take.+while) automatically behind the scenes. The search may be limited to the namespace NS, and may optionally search doc strings \(based on DOCS-P), include private vars (based on INCLUDE-PRIVATE-P), and be case-sensitive (based on CASE-SENSITIVE-P)." (concat (if case-sensitive-p "Case-sensitive " "") (if docs-p "Documentation " "") (format "Apropos for %S" query) (if ns (format " in namespace %S" ns) "") (if include-private-p " (public and private symbols)" " (public symbols only)"))) (defun cider-apropos-highlight (doc query) "Return the DOC string propertized to highlight QUERY matches." (let ((pos 0)) (while (string-match query doc pos) (setq pos (match-end 0)) (put-text-property (match-beginning 0) (match-end 0) 'font-lock-face apropos-match-face doc))) doc) (defun cider-apropos-result (result query docs-p) "Emit a RESULT matching QUERY into current buffer, formatted for DOCS-P." (nrepl-dbind-response result (name type doc) (let* ((label (capitalize (if (string= type "variable") "var" type))) (help (concat "Display doc for this " (downcase label)))) (cider-propertize-region (list 'apropos-symbol name 'action 'cider-apropos-doc 'help-echo help) (insert-text-button name 'type 'apropos-symbol) (insert "\n ") (insert-text-button label 'type (intern (concat "apropos-" type))) (insert ": ") (let ((beg (point))) (if docs-p (insert (cider-apropos-highlight doc query) "\n") (insert doc) (fill-region beg (point)))) (insert "\n"))))) (declare-function cider-mode "cider-mode") (defun cider-show-apropos (summary results query docs-p) "Show SUMMARY and RESULTS for QUERY in a pop-up buffer, formatted for DOCS-P." (with-current-buffer (cider-popup-buffer cider-apropos-buffer t) (let ((inhibit-read-only t)) (apropos-mode) (if (boundp 'header-line-format) (setq-local header-line-format summary) (insert summary "\n\n")) (dolist (result results) (cider-apropos-result result query docs-p)) (goto-char (point-min))))) ;;;###autoload (defun cider-apropos (query &optional ns docs-p privates-p case-sensitive-p) "Show all symbols whose names match QUERY, a regular expression. QUERY can also be a list of space-separated words (e.g. take while) which will be converted to a regular expression (like take.+while) automatically behind the scenes. The search may be limited to the namespace NS, and may optionally search doc strings (based on DOCS-P), include private vars \(based on PRIVATES-P), and be case-sensitive (based on CASE-SENSITIVE-P)." (interactive (cons (read-string "Search for Clojure symbol (a regular expression): ") (when current-prefix-arg (list (let ((ns (completing-read "Namespace (default is all): " (cider-sync-request:ns-list)))) (if (string= ns "") nil ns)) (y-or-n-p "Search doc strings? ") (y-or-n-p "Include private symbols? ") (y-or-n-p "Case-sensitive? "))))) (cider-ensure-connected) (cider-ensure-op-supported "apropos") (if-let ((summary (cider-apropos-summary query ns docs-p privates-p case-sensitive-p)) (results (cider-sync-request:apropos query ns docs-p privates-p case-sensitive-p))) (cider-show-apropos summary results query docs-p) (message "No apropos matches for %S" query))) ;;;###autoload (defun cider-apropos-documentation () "Shortcut for (cider-apropos nil t)." (interactive) (cider-ensure-connected) (cider-ensure-op-supported "apropos") (cider-apropos (read-string "Search for Clojure documentation (a regular expression): ") nil t)) (defun cider-apropos-act-on-symbol (symbol) "Apply selected action on SYMBOL." (let ((action (completing-read (format "Choose action to apply to `%s`: " symbol) '("display-doc" "find-def" "lookup-on-grimoire" "quit")))) (pcase action ("display-doc" (cider-doc-lookup symbol)) ("find-def" (cider--find-var symbol)) ("lookup-on-grimoire" (cider-grimoire-lookup symbol)) ("quit" nil) (_ (user-error "Unknown action `%s`" action))))) ;;;###autoload (defun cider-apropos-select (query &optional ns docs-p privates-p case-sensitive-p) "Similar to `cider-apropos', but presents the results in a completing read. Show all symbols whose names match QUERY, a regular expression. QUERY can also be a list of space-separated words (e.g. take while) which will be converted to a regular expression (like take.+while) automatically behind the scenes. The search may be limited to the namespace NS, and may optionally search doc strings (based on DOCS-P), include private vars \(based on PRIVATES-P), and be case-sensitive (based on CASE-SENSITIVE-P)." (interactive (cons (read-string "Search for Clojure symbol (a regular expression): ") (when current-prefix-arg (list (let ((ns (completing-read "Namespace (default is all): " (cider-sync-request:ns-list)))) (if (string= ns "") nil ns)) (y-or-n-p "Search doc strings? ") (y-or-n-p "Include private symbols? ") (y-or-n-p "Case-sensitive? "))))) (cider-ensure-connected) (cider-ensure-op-supported "apropos") (if-let ((summary (cider-apropos-summary query ns docs-p privates-p case-sensitive-p)) (results (mapcar (lambda (r) (nrepl-dict-get r "name")) (cider-sync-request:apropos query ns docs-p privates-p case-sensitive-p)))) (cider-apropos-act-on-symbol (completing-read (concat summary ": ") results)) (message "No apropos matches for %S" query))) ;;;###autoload (defun cider-apropos-documentation-select () "Shortcut for (cider-apropos-select nil t)." (interactive) (cider-ensure-connected) (cider-ensure-op-supported "apropos") (cider-apropos (read-string "Search for Clojure documentation (a regular expression): ") nil t)) (provide 'cider-apropos) ;;; cider-apropos.el ends here