|
|
- ;;; cider-xref.el --- Xref functionality for Clojure -*- lexical-binding: t -*-
-
- ;; Copyright © 2019 Bozhidar Batsov and CIDER contributors
- ;;
- ;; Author: Bozhidar Batsov <bozhidar@batsov.com>
-
- ;; 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 <http://www.gnu.org/licenses/>.
-
- ;; This file is not part of GNU Emacs.
-
- ;;; Commentary:
-
- ;; Xref (find usages) functionality for Clojure. The implementation is based on
- ;; ideas from this article https://metaredux.com/posts/2019/05/04/discovering-runtime-function-references-in-clojure.html.
- ;;
- ;; Keep in mind that you won't get references in namespaces that haven't been loaded yet.
-
- ;;; Code:
-
- (require 'cider-doc)
- (require 'cider-util)
- (require 'subr-x)
- (require 'cider-compat)
-
- (require 'cider-client)
- (require 'cider-popup)
- (require 'nrepl-dict)
-
- (require 'clojure-mode)
- (require 'apropos)
- (require 'button)
-
- (defconst cider-xref-buffer "*cider-xref*")
-
- (defcustom cider-xref-actions '(("display-doc" . cider-doc-lookup)
- ("find-def" . cider--find-var)
- ("lookup-on-grimoire" . cider-grimoire-lookup))
- "Controls the actions to be applied on the symbol found by an xref search.
- The first action key in the list will be selected as default. If the list
- contains only one action key, the associated action function will be
- applied automatically. An action function can be any function that receives
- the symbol found by the xref search as argument."
- :type '(alist :key-type string :value-type function)
- :group 'cider
- :package-version '(cider . "0.22.0"))
-
- (defun cider-xref-doc (button)
- "Display documentation for the symbol represented at BUTTON."
- (cider-doc-lookup (button-get button 'apropos-symbol)))
-
- (defun cider-xref-result (result)
- "Emit a RESULT into current buffer."
- (let ((var-name (nrepl-dict-get result "name")))
- (cider-propertize-region (list 'apropos-symbol var-name
- 'action 'cider-xref-doc
- 'help-echo "Display doc")
- (insert-text-button var-name 'type 'apropos-symbol)
- (insert "\n ")
- (insert-text-button "Function" 'type 'apropos-function)
- (insert ": ")
- (let ((beg (point)))
- (insert (nrepl-dict-get result "doc"))
- (fill-region beg (point)))
- (insert "\n")
- (if-let* ((file (nrepl-dict-get result "file"))
- (line (nrepl-dict-get result "line")))
- (progn
- (insert (propertize var-name
- 'font-lock-face 'font-lock-function-name-face)
- " is defined in ")
- (insert-text-button (cider--abbreviate-file-protocol file)
- 'follow-link t
- 'action (lambda (_x)
- (cider-xref-source file line var-name)))
- (insert "."))
- (insert "Definition location unavailable."))
- (insert "\n"))))
-
- (defun cider-xref-source (file line name)
- "Find source for FILE, LINE and NAME."
- (interactive)
- (if file
- (if-let* ((buffer (and (not (cider--tooling-file-p file))
- (cider-find-file file))))
- (cider-jump-to buffer (if line
- (cons line nil)
- name)
- nil)
- (user-error
- (substitute-command-keys
- "Can't find the source because it wasn't defined with `cider-eval-buffer'")))
- (error "No source location for %s" name)))
-
- (declare-function cider-mode "cider-mode")
-
- (defun cider-show-xref (summary results)
- "Show SUMMARY and RESULTS in a pop-up buffer."
- (with-current-buffer (cider-popup-buffer cider-xref-buffer 'select 'apropos-mode 'ancillary)
- (let ((inhibit-read-only t))
- (if (boundp 'header-line-format)
- (setq-local header-line-format summary)
- (insert summary "\n\n"))
- (dolist (result results)
- (cider-xref-result result))
- (goto-char (point-min)))))
-
- ;;;###autoload
- (defun cider-xref-fn-refs (&optional ns symbol)
- "Show all functions that reference the var matching NS and SYMBOL."
- (interactive)
- (cider-ensure-connected)
- (cider-ensure-op-supported "fn-refs")
- (if-let* ((ns (or ns (cider-current-ns)))
- (symbol (or symbol (cider-symbol-at-point)))
- (results (cider-sync-request:fn-refs ns symbol)))
- (cider-show-xref (format "Showing %d functions that reference %s in currently loaded namespaces" (length results) symbol) results)
- (message "No references found for %S in currently loaded namespaces" symbol)))
-
- ;;;###autoload
- (defun cider-xref-fn-deps (&optional ns symbol)
- "Show all functions referenced by the var matching NS and SYMBOL."
- (interactive)
- (cider-ensure-connected)
- (cider-ensure-op-supported "fn-deps")
- (if-let* ((ns (or ns (cider-current-ns)))
- (symbol (or symbol (cider-symbol-at-point)))
- (results (cider-sync-request:fn-deps ns symbol)))
- (cider-show-xref (format "Showing %d function dependencies for %s" (length results) symbol) results)
- (message "No dependencies found for %S" symbol)))
-
- (defun cider-xref-act-on-symbol (symbol)
- "Apply selected action on SYMBOL."
- (let* ((first-action-key (car (car cider-xref-actions)))
- (action-key (if (= 1 (length cider-xref-actions))
- first-action-key
- (completing-read (format "Choose action to apply to `%s` (default %s): "
- symbol first-action-key)
- cider-xref-actions nil nil nil nil first-action-key)))
- (action-fn (cdr (assoc action-key cider-xref-actions))))
- (if action-fn
- (funcall action-fn symbol)
- (user-error "Unknown action `%s`" action-key))))
-
- ;;;###autoload
- (defun cider-xref-fn-refs-select (&optional ns symbol)
- "Displays the references for NS and SYMBOL using completing read."
- (interactive)
- (cider-ensure-connected)
- (cider-ensure-op-supported "fn-refs")
- (if-let* ((ns (or ns (cider-current-ns)))
- (symbol (or symbol (cider-symbol-at-point)))
- (results (mapcar (lambda (d) (nrepl-dict-get d "name")) (cider-sync-request:fn-refs ns symbol)))
- (summary (format "References for %s" symbol)))
- (cider-xref-act-on-symbol (completing-read (concat summary ": ") results))
- (message "No references for %S found" symbol)))
-
- ;;;###autoload
- (defun cider-xref-fn-deps-select (&optional ns symbol)
- "Displays the function dependencies for NS and SYMBOL using completing read."
- (interactive)
- (cider-ensure-connected)
- (cider-ensure-op-supported "fn-deps")
- (if-let* ((ns (or ns (cider-current-ns)))
- (symbol (or symbol (cider-symbol-at-point)))
- (results (mapcar (lambda (d) (nrepl-dict-get d "name")) (cider-sync-request:fn-deps ns symbol)))
- (summary (format "Dependencies for %s" symbol)))
- (cider-xref-act-on-symbol (completing-read (concat summary ": ") results))
- (message "No dependencies for %S found" symbol)))
-
- (provide 'cider-xref)
-
- ;;; cider-xref.el ends here
|