aboutsummaryrefslogtreecommitdiff
path: root/.config/emacs/lisp/libs/elisp-refs.el
diff options
context:
space:
mode:
Diffstat (limited to '.config/emacs/lisp/libs/elisp-refs.el')
-rw-r--r--.config/emacs/lisp/libs/elisp-refs.el913
1 files changed, 0 insertions, 913 deletions
diff --git a/.config/emacs/lisp/libs/elisp-refs.el b/.config/emacs/lisp/libs/elisp-refs.el
deleted file mode 100644
index 21b3546..0000000
--- a/.config/emacs/lisp/libs/elisp-refs.el
+++ /dev/null
@@ -1,913 +0,0 @@
-;;; elisp-refs.el --- find callers of elisp functions or macros -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2016-2020 Wilfred Hughes <me@wilfred.me.uk>
-
-;; Author: Wilfred Hughes <me@wilfred.me.uk>
-;; Version: 1.6
-;; Keywords: lisp
-;; Package-Requires: ((dash "2.12.0") (s "1.11.0"))
-
-;; 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/>.
-
-;;; Commentary:
-
-;; elisp-refs.el is an Emacs package for finding references to
-;; functions, macros or variables. Unlike a dumb text search,
-;; elisp-refs.el actually parses the code, so it's never confused by
-;; comments or `foo-bar' matching `foo'.
-;;
-;; See https://github.com/Wilfred/refs.el/blob/master/README.md for
-;; more information.
-
-;;; Code:
-
-(require 'dash)
-(require 's)
-(require 'format)
-(eval-when-compile (require 'cl-lib))
-
-(defvar symbols-with-pos-enabled)
-(declare-function symbol-with-pos-p nil (object))
-(declare-function symbol-with-pos-pos nil (ls))
-
-;;; Internal
-
-(defvar elisp-refs-verbose t)
-
-(defun elisp-refs--format-int (integer)
- "Format INTEGER as a string, with , separating thousands."
- (let ((number (abs integer))
- (parts nil))
- (while (> number 999)
- (push (format "%03d" (mod number 1000))
- parts)
- (setq number (/ number 1000)))
- (push (format "%d" number) parts)
- (concat
- (if (< integer 0) "-" "")
- (s-join "," parts))))
-
-(defsubst elisp-refs--start-pos (end-pos)
- "Find the start position of form ending at END-POS
-in the current buffer."
- (let ((parse-sexp-ignore-comments t))
- (scan-sexps end-pos -1)))
-
-(defun elisp-refs--sexp-positions (buffer start-pos end-pos)
- "Return a list of start and end positions of all the sexps
-between START-POS and END-POS (inclusive) in BUFFER.
-
-Positions exclude quote characters, so given 'foo or `foo, we
-report the position of the symbol foo.
-
-Not recursive, so we don't consider subelements of nested sexps."
- (let ((positions nil))
- (with-current-buffer buffer
- (condition-case _err
- (catch 'done
- (while t
- (let* ((sexp-end-pos (let ((parse-sexp-ignore-comments t))
- (scan-sexps start-pos 1))))
- ;; If we've reached a sexp beyond the range requested,
- ;; or if there are no sexps left, we're done.
- (when (or (null sexp-end-pos) (> sexp-end-pos end-pos))
- (throw 'done nil))
- ;; Otherwise, this sexp is in the range requested.
- (push (list (elisp-refs--start-pos sexp-end-pos) sexp-end-pos)
- positions)
- (setq start-pos sexp-end-pos))))
- ;; Terminate when we see "Containing expression ends prematurely"
- (scan-error nil)))
- (nreverse positions)))
-
-(defun elisp-refs--read-buffer-form (symbols-with-pos)
- "Read a form from the current buffer, starting at point.
-Returns a list:
-\(form form-start-pos form-end-pos symbol-positions read-start-pos)
-
-In Emacs 28 and earlier, SYMBOL-POSITIONS is a list of 0-indexed
-symbol positions relative to READ-START-POS, according to
-`read-symbol-positions-list'.
-
-In Emacs 29+, SYMBOL-POSITIONS is nil. If SYMBOLS-WITH-POS is
-non-nil, forms are read with `read-positioning-symbols'."
- (let* ((read-with-symbol-positions t)
- (read-start-pos (point))
- (form (if (and symbols-with-pos (fboundp 'read-positioning-symbols))
- (read-positioning-symbols (current-buffer))
- (read (current-buffer))))
- (symbols (if (boundp 'read-symbol-positions-list)
- read-symbol-positions-list
- nil))
- (end-pos (point))
- (start-pos (elisp-refs--start-pos end-pos)))
- (list form start-pos end-pos symbols read-start-pos)))
-
-(defvar elisp-refs--path nil
- "A buffer-local variable used by `elisp-refs--contents-buffer'.
-Internal implementation detail.")
-
-(defun elisp-refs--read-all-buffer-forms (buffer symbols-with-pos)
- "Read all the forms in BUFFER, along with their positions."
- (with-current-buffer buffer
- (goto-char (point-min))
- (let ((forms nil))
- (condition-case err
- (while t
- (push (elisp-refs--read-buffer-form symbols-with-pos) forms))
- (error
- (if (or (equal (car err) 'end-of-file)
- ;; TODO: this shouldn't occur in valid elisp files,
- ;; but it's happening in helm-utils.el.
- (equal (car err) 'scan-error))
- ;; Reached end of file, we're done.
- (nreverse forms)
- ;; Some unexpected error, propagate.
- (error "Unexpected error whilst reading %s position %s: %s"
- (abbreviate-file-name elisp-refs--path) (point) err)))))))
-
-(defun elisp-refs--proper-list-p (val)
- "Is VAL a proper list?"
- (if (fboundp 'proper-list-p)
- ;; `proper-list-p' was added in Emacs 27.1.
- ;; http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=2fde6275b69fd113e78243790bf112bbdd2fe2bf
- (with-no-warnings (proper-list-p val))
- ;; Earlier Emacs versions only had format-proper-list-p.
- (with-no-warnings (format-proper-list-p val))))
-
-(defun elisp-refs--walk (buffer form start-pos end-pos symbol match-p &optional path)
- "Walk FORM, a nested list, and return a list of sublists (with
-their positions) where MATCH-P returns t. FORM is traversed
-depth-first (pre-order traversal, left-to-right).
-
-MATCH-P is called with three arguments:
-\(SYMBOL CURRENT-FORM PATH).
-
-PATH is the first element of all the enclosing forms of
-CURRENT-FORM, innermost first, along with the index of the
-current form.
-
-For example if we are looking at h in (e f (g h)), PATH takes the
-value ((g . 1) (e . 2)).
-
-START-POS and END-POS should be the position of FORM within BUFFER."
- (cond
- ((funcall match-p symbol form path)
- ;; If this form matches, just return it, along with the position.
- (list (list form start-pos end-pos)))
- ;; Otherwise, recurse on the subforms.
- ((consp form)
- (let ((matches nil)
- ;; Find the positions of the subforms.
- (subforms-positions
- (if (eq (car-safe form) '\`)
- ;; Kludge: `elisp-refs--sexp-positions' excludes the ` when
- ;; calculating positions. So, to find the inner
- ;; positions when walking from `(...) to (...), we
- ;; don't need to increment the start position.
- (cons nil (elisp-refs--sexp-positions buffer start-pos end-pos))
- ;; Calculate the positions after the opening paren.
- (elisp-refs--sexp-positions buffer (1+ start-pos) end-pos))))
- ;; For each subform, recurse if it's a list, or a matching symbol.
- (--each (-zip-pair form subforms-positions)
- (-let [(subform subform-start subform-end) it]
- (when (or
- (and (consp subform) (elisp-refs--proper-list-p subform))
- (and (symbolp subform) (eq subform symbol)))
- (-when-let (subform-matches
- (elisp-refs--walk
- buffer subform
- subform-start subform-end
- symbol match-p
- (cons (cons (car-safe form) it-index) path)))
- (push subform-matches matches)))))
-
- ;; Concat the results from all the subforms.
- (apply #'append (nreverse matches))))))
-
-;; TODO: condition-case (condition-case ... (error ...)) is not a call
-;; TODO: (cl-destructuring-bind (foo &rest bar) ...) is not a call
-;; TODO: letf, cl-letf, -let, -let*
-(defun elisp-refs--function-p (symbol form path)
- "Return t if FORM looks like a function call to SYMBOL."
- (cond
- ((not (consp form))
- nil)
- ;; Ignore (defun _ (SYMBOL ...) ...)
- ((or (equal (car path) '(defsubst . 2))
- (equal (car path) '(defun . 2))
- (equal (car path) '(defmacro . 2))
- (equal (car path) '(cl-defun . 2)))
- nil)
- ;; Ignore (lambda (SYMBOL ...) ...)
- ((equal (car path) '(lambda . 1))
- nil)
- ;; Ignore (let (SYMBOL ...) ...)
- ;; and (let* (SYMBOL ...) ...)
- ((or
- (equal (car path) '(let . 1))
- (equal (car path) '(let* . 1)))
- nil)
- ;; Ignore (let ((SYMBOL ...)) ...)
- ((or
- (equal (cl-second path) '(let . 1))
- (equal (cl-second path) '(let* . 1)))
- nil)
- ;; Ignore (declare-function NAME (ARGS...))
- ((equal (car path) '(declare-function . 3))
- nil)
- ;; (SYMBOL ...)
- ((eq (car form) symbol)
- t)
- ;; (foo ... #'SYMBOL ...)
- ((--any-p (equal it (list 'function symbol)) form)
- t)
- ;; (funcall 'SYMBOL ...)
- ((and (eq (car form) 'funcall)
- (equal `',symbol (cl-second form)))
- t)
- ;; (apply 'SYMBOL ...)
- ((and (eq (car form) 'apply)
- (equal `',symbol (cl-second form)))
- t)))
-
-(defun elisp-refs--macro-p (symbol form path)
- "Return t if FORM looks like a macro call to SYMBOL."
- (cond
- ((not (consp form))
- nil)
- ;; Ignore (defun _ (SYMBOL ...) ...)
- ((or (equal (car path) '(defsubst . 2))
- (equal (car path) '(defun . 2))
- (equal (car path) '(defmacro . 2)))
- nil)
- ;; Ignore (lambda (SYMBOL ...) ...)
- ((equal (car path) '(lambda . 1))
- nil)
- ;; Ignore (let (SYMBOL ...) ...)
- ;; and (let* (SYMBOL ...) ...)
- ((or
- (equal (car path) '(let . 1))
- (equal (car path) '(let* . 1)))
- nil)
- ;; Ignore (let ((SYMBOL ...)) ...)
- ((or
- (equal (cl-second path) '(let . 1))
- (equal (cl-second path) '(let* . 1)))
- nil)
- ;; (SYMBOL ...)
- ((eq (car form) symbol)
- t)))
-
-;; Looking for a special form is exactly the same as looking for a
-;; macro.
-(defalias 'elisp-refs--special-p 'elisp-refs--macro-p)
-
-(defun elisp-refs--variable-p (symbol form path)
- "Return t if this looks like a variable reference to SYMBOL.
-We consider parameters to be variables too."
- (cond
- ((consp form)
- nil)
- ;; Ignore (defun _ (SYMBOL ...) ...)
- ((or (equal (car path) '(defsubst . 1))
- (equal (car path) '(defun . 1))
- (equal (car path) '(defmacro . 1))
- (equal (car path) '(cl-defun . 1)))
- nil)
- ;; (let (SYMBOL ...) ...) is a variable, not a function call.
- ((or
- (equal (cl-second path) '(let . 1))
- (equal (cl-second path) '(let* . 1)))
- t)
- ;; (lambda (SYMBOL ...) ...) is a variable
- ((equal (cl-second path) '(lambda . 1))
- t)
- ;; (let ((SYMBOL ...)) ...) is also a variable.
- ((or
- (equal (cl-third path) '(let . 1))
- (equal (cl-third path) '(let* . 1)))
- t)
- ;; Ignore (SYMBOL ...) otherwise, we assume it's a function/macro
- ;; call.
- ((equal (car path) (cons symbol 0))
- nil)
- ((eq form symbol)
- t)))
-
-;; TODO: benchmark building a list with `push' rather than using
-;; mapcat.
-(defun elisp-refs--read-and-find (buffer symbol match-p)
- "Read all the forms in BUFFER, and return a list of all forms that
-contain SYMBOL where MATCH-P returns t.
-
-For every matching form found, we return the form itself along
-with its start and end position."
- (-non-nil
- (--mapcat
- (-let [(form start-pos end-pos symbol-positions _read-start-pos) it]
- ;; Optimisation: if we have a list of positions for the current
- ;; form (Emacs 28 and earlier), and it doesn't contain the
- ;; symbol we're looking for, don't bother walking the form.
- (when (or (null symbol-positions) (assq symbol symbol-positions))
- (elisp-refs--walk buffer form start-pos end-pos symbol match-p)))
- (elisp-refs--read-all-buffer-forms buffer nil))))
-
-(defun elisp-refs--walk-positioned-symbols (forms symbol)
- "Given a nested list of FORMS, return a list of all positions of SYMBOL.
-Assumes `symbol-with-pos-pos' is defined (Emacs 29+)."
- (cond
- ((symbol-with-pos-p forms)
- (let ((symbols-with-pos-enabled t))
- (if (eq forms symbol)
- (list (list symbol
- (symbol-with-pos-pos forms)
- (+ (symbol-with-pos-pos forms) (length (symbol-name symbol))))))))
- ((elisp-refs--proper-list-p forms)
- ;; Proper list, use `--mapcat` to reduce how much we recurse.
- (--mapcat (elisp-refs--walk-positioned-symbols it symbol) forms))
- ((consp forms)
- ;; Improper list, we have to recurse on head and tail.
- (append (elisp-refs--walk-positioned-symbols (car forms) symbol)
- (elisp-refs--walk-positioned-symbols (cdr forms) symbol)))
- ((vectorp forms)
- (--mapcat (elisp-refs--walk-positioned-symbols it symbol) forms))))
-
-(defun elisp-refs--read-and-find-symbol (buffer symbol)
- "Read all the forms in BUFFER, and return a list of all
-positions of SYMBOL."
- (let* ((symbols-with-pos (fboundp 'symbol-with-pos-pos))
- (forms (elisp-refs--read-all-buffer-forms buffer symbols-with-pos)))
-
- (if symbols-with-pos
- (elisp-refs--walk-positioned-symbols forms symbol)
- (-non-nil
- (--mapcat
- (-let [(_ _ _ symbol-positions read-start-pos) it]
- (--map
- (-let [(sym . offset) it]
- (when (eq sym symbol)
- (-let* ((start-pos (+ read-start-pos offset))
- (end-pos (+ start-pos (length (symbol-name sym)))))
- (list sym start-pos end-pos))))
- symbol-positions))
- forms)))))
-
-(defun elisp-refs--filter-obarray (pred)
- "Return a list of all the items in `obarray' where PRED returns t."
- (let (symbols)
- (mapatoms (lambda (symbol)
- (when (and (funcall pred symbol)
- (not (equal (symbol-name symbol) "")))
- (push symbol symbols))))
- symbols))
-
-(defun elisp-refs--loaded-paths ()
- "Return a list of all files that have been loaded in Emacs.
-Where the file was a .elc, return the path to the .el file instead."
- (let ((elc-paths (-non-nil (mapcar #'-first-item load-history))))
- (-non-nil
- (--map
- (let ((el-name (format "%s.el" (file-name-sans-extension it)))
- (el-gz-name (format "%s.el.gz" (file-name-sans-extension it))))
- (cond ((file-exists-p el-name) el-name)
- ((file-exists-p el-gz-name) el-gz-name)
- ;; Ignore files where we can't find a .el file.
- (t nil)))
- elc-paths))))
-
-(defun elisp-refs--contents-buffer (path)
- "Read PATH into a disposable buffer, and return it.
-Works around the fact that Emacs won't allow multiple buffers
-visiting the same file."
- (let ((fresh-buffer (generate-new-buffer (format " *refs-%s*" path)))
- ;; Be defensive against users overriding encoding
- ;; configurations (Helpful bugs #75 and #147).
- (coding-system-for-read nil)
- (file-name-handler-alist
- '(("\\(?:\\.dz\\|\\.txz\\|\\.xz\\|\\.lzma\\|\\.lz\\|\\.g?z\\|\\.\\(?:tgz\\|svgz\\|sifz\\)\\|\\.tbz2?\\|\\.bz2\\|\\.Z\\)\\(?:~\\|\\.~[-[:alnum:]:#@^._]+\\(?:~[[:digit:]]+\\)?~\\)?\\'" .
- jka-compr-handler)
- ("\\(?:^/\\)\\(\\(?:\\(?:\\(-\\|[[:alnum:]]\\{2,\\}\\)\\(?::\\)\\(?:\\([^/:|[:blank:]]+\\)\\(?:@\\)\\)?\\(\\(?:[%._[:alnum:]-]+\\|\\(?:\\[\\)\\(?:\\(?:[[:alnum:]]*:\\)+[.[:alnum:]]*\\)?\\(?:]\\)\\)\\(?:\\(?:#\\)\\(?:[[:digit:]]+\\)\\)?\\)?\\)\\(?:|\\)\\)+\\)?\\(?:\\(-\\|[[:alnum:]]\\{2,\\}\\)\\(?::\\)\\(?:\\([^/:|[:blank:]]+\\)\\(?:@\\)\\)?\\(\\(?:[%._[:alnum:]-]+\\|\\(?:\\[\\)\\(?:\\(?:[[:alnum:]]*:\\)+[.[:alnum:]]*\\)?\\(?:]\\)\\)\\(?:\\(?:#\\)\\(?:[[:digit:]]+\\)\\)?\\)?\\)\\(?::\\)\\([^\n ]*\\'\\)" . tramp-file-name-handler)
- ("\\`/:" . file-name-non-special))))
- (with-current-buffer fresh-buffer
- (setq-local elisp-refs--path path)
- (insert-file-contents path)
- ;; We don't enable emacs-lisp-mode because it slows down this
- ;; function significantly. We just need the syntax table for
- ;; scan-sexps to do the right thing with comments.
- (set-syntax-table emacs-lisp-mode-syntax-table))
- fresh-buffer))
-
-(defvar elisp-refs--highlighting-buffer
- nil
- "A temporary buffer used for highlighting.
-Since `elisp-refs--syntax-highlight' is a hot function, we
-don't want to create lots of temporary buffers.")
-
-(defun elisp-refs--syntax-highlight (str)
- "Apply font-lock properties to a string STR of Emacs lisp code."
- ;; Ensure we have a highlighting buffer to work with.
- (unless (and elisp-refs--highlighting-buffer
- (buffer-live-p elisp-refs--highlighting-buffer))
- (setq elisp-refs--highlighting-buffer
- (generate-new-buffer " *refs-highlighting*"))
- (with-current-buffer elisp-refs--highlighting-buffer
- (delay-mode-hooks (emacs-lisp-mode))))
-
- (with-current-buffer elisp-refs--highlighting-buffer
- (erase-buffer)
- (insert str)
- (if (fboundp 'font-lock-ensure)
- (font-lock-ensure)
- (with-no-warnings
- (font-lock-fontify-buffer)))
- (buffer-string)))
-
-(defun elisp-refs--replace-tabs (string)
- "Replace tabs in STRING with spaces."
- ;; This is important for unindenting, as we may unindent by less
- ;; than one whole tab.
- (s-replace "\t" (s-repeat tab-width " ") string))
-
-(defun elisp-refs--lines (string)
- "Return a list of all the lines in STRING.
-'a\nb' -> ('a\n' 'b')"
- (let ((lines nil))
- (while (> (length string) 0)
- (let ((index (s-index-of "\n" string)))
- (if index
- (progn
- (push (substring string 0 (1+ index)) lines)
- (setq string (substring string (1+ index))))
- (push string lines)
- (setq string ""))))
- (nreverse lines)))
-
-(defun elisp-refs--map-lines (string fn)
- "Execute FN for each line in string, and join the result together."
- (let ((result nil))
- (dolist (line (elisp-refs--lines string))
- (push (funcall fn line) result))
- (apply #'concat (nreverse result))))
-
-(defun elisp-refs--unindent-rigidly (string)
- "Given an indented STRING, unindent rigidly until
-at least one line has no indent.
-
-STRING should have a 'elisp-refs-start-pos property. The returned
-string will have this property updated to reflect the unindent."
- (let* ((lines (s-lines string))
- ;; Get the leading whitespace for each line.
- (indents (--map (car (s-match (rx bos (+ whitespace)) it))
- lines))
- (min-indent (-min (--map (length it) indents))))
- (propertize
- (elisp-refs--map-lines
- string
- (lambda (line) (substring line min-indent)))
- 'elisp-refs-unindented min-indent)))
-
-(defun elisp-refs--containing-lines (buffer start-pos end-pos)
- "Return a string, all the lines in BUFFER that are between
-START-POS and END-POS (inclusive).
-
-For the characters that are between START-POS and END-POS,
-propertize them."
- (let (expanded-start-pos expanded-end-pos)
- (with-current-buffer buffer
- ;; Expand START-POS and END-POS to line boundaries.
- (goto-char start-pos)
- (beginning-of-line)
- (setq expanded-start-pos (point))
- (goto-char end-pos)
- (end-of-line)
- (setq expanded-end-pos (point))
-
- ;; Extract the rest of the line before and after the section we're interested in.
- (let* ((before-match (buffer-substring expanded-start-pos start-pos))
- (after-match (buffer-substring end-pos expanded-end-pos))
- ;; Concat the extra text with the actual match, ensuring we
- ;; highlight the match as code, but highlight the rest as as
- ;; comments.
- (text (concat
- (propertize before-match
- 'face 'font-lock-comment-face)
- (elisp-refs--syntax-highlight (buffer-substring start-pos end-pos))
- (propertize after-match
- 'face 'font-lock-comment-face))))
- (-> text
- (elisp-refs--replace-tabs)
- (elisp-refs--unindent-rigidly)
- (propertize 'elisp-refs-start-pos expanded-start-pos
- 'elisp-refs-path elisp-refs--path))))))
-
-(defun elisp-refs--find-file (button)
- "Open the file referenced by BUTTON."
- (find-file (button-get button 'path))
- (goto-char (point-min)))
-
-(define-button-type 'elisp-refs-path-button
- 'action 'elisp-refs--find-file
- 'follow-link t
- 'help-echo "Open file")
-
-(defun elisp-refs--path-button (path)
- "Return a button that navigates to PATH."
- (with-temp-buffer
- (insert-text-button
- (abbreviate-file-name path)
- :type 'elisp-refs-path-button
- 'path path)
- (buffer-string)))
-
-(defun elisp-refs--describe (button)
- "Show *Help* for the symbol referenced by BUTTON."
- (let ((symbol (button-get button 'symbol))
- (kind (button-get button 'kind)))
- (cond ((eq kind 'symbol)
- (describe-symbol symbol))
- ((eq kind 'variable)
- (describe-variable symbol))
- (t
- ;; Emacs uses `describe-function' for functions, macros and
- ;; special forms.
- (describe-function symbol)))))
-
-(define-button-type 'elisp-refs-describe-button
- 'action 'elisp-refs--describe
- 'follow-link t
- 'help-echo "Describe")
-
-(defun elisp-refs--describe-button (symbol kind)
- "Return a button that shows *Help* for SYMBOL.
-KIND should be 'function, 'macro, 'variable, 'special or 'symbol."
- (with-temp-buffer
- (insert (symbol-name kind) " ")
- (insert-text-button
- (symbol-name symbol)
- :type 'elisp-refs-describe-button
- 'symbol symbol
- 'kind kind)
- (buffer-string)))
-
-(defun elisp-refs--pluralize (number thing)
- "Human-friendly description of NUMBER occurrences of THING."
- (format "%s %s%s"
- (elisp-refs--format-int number)
- thing
- (if (equal number 1) "" "s")))
-
-(defun elisp-refs--format-count (symbol ref-count file-count
- searched-file-count prefix)
- (let* ((file-str (if (zerop file-count)
- ""
- (format " in %s" (elisp-refs--pluralize file-count "file"))))
- (found-str (format "Found %s to %s%s."
- (elisp-refs--pluralize ref-count "reference")
- symbol
- file-str))
- (searched-str (if prefix
- (format "Searched %s in %s."
- (elisp-refs--pluralize searched-file-count "loaded file")
- (elisp-refs--path-button (file-name-as-directory prefix)))
- (format "Searched all %s loaded in Emacs."
- (elisp-refs--pluralize searched-file-count "file")))))
- (s-word-wrap 70 (format "%s %s" found-str searched-str))))
-
-;; TODO: if we have multiple matches on one line, we repeatedly show
-;; that line. That's slightly confusing.
-(defun elisp-refs--show-results (symbol description results
- searched-file-count prefix)
- "Given a RESULTS list where each element takes the form \(forms . buffer\),
-render a friendly results buffer."
- (let ((buf (get-buffer-create (format "*refs: %s*" symbol))))
- (switch-to-buffer buf)
- (let ((inhibit-read-only t))
- (erase-buffer)
- (save-excursion
- ;; Insert the header.
- (insert
- (elisp-refs--format-count
- description
- (-sum (--map (length (car it)) results))
- (length results)
- searched-file-count
- prefix)
- "\n\n")
- ;; Insert the results.
- (--each results
- (-let* (((forms . buf) it)
- (path (with-current-buffer buf elisp-refs--path)))
- (insert
- (propertize "File: " 'face 'bold)
- (elisp-refs--path-button path) "\n")
- (--each forms
- (-let [(_ start-pos end-pos) it]
- (insert (elisp-refs--containing-lines buf start-pos end-pos)
- "\n")))
- (insert "\n")))
- ;; Prepare the buffer for the user.
- (elisp-refs-mode)))
- ;; Cleanup buffers created when highlighting results.
- (when elisp-refs--highlighting-buffer
- (kill-buffer elisp-refs--highlighting-buffer))))
-
-(defun elisp-refs--loaded-bufs ()
- "Return a list of open buffers, one for each path in `load-path'."
- (mapcar #'elisp-refs--contents-buffer (elisp-refs--loaded-paths)))
-
-(defun elisp-refs--search-1 (bufs match-fn)
- "Call MATCH-FN on each buffer in BUFS, reporting progress
-and accumulating results.
-
-BUFS should be disposable: we make no effort to preserve their
-state during searching.
-
-MATCH-FN should return a list where each element takes the form:
-\(form start-pos end-pos)."
- (let* (;; Our benchmark suggests we spend a lot of time in GC, and
- ;; performance improves if we GC less frequently.
- (gc-cons-percentage 0.8)
- (total-bufs (length bufs)))
- (let ((searched 0)
- (forms-and-bufs nil))
- (dolist (buf bufs)
- (let* ((matching-forms (funcall match-fn buf)))
- ;; If there were any matches in this buffer, push the
- ;; matches along with the buffer into our results
- ;; list.
- (when matching-forms
- (push (cons matching-forms buf) forms-and-bufs))
- ;; Give feedback to the user on our progress, because
- ;; searching takes several seconds.
- (when (and (zerop (mod searched 10))
- elisp-refs-verbose)
- (message "Searched %s/%s files" searched total-bufs))
- (cl-incf searched)))
- (when elisp-refs-verbose
- (message "Searched %s/%s files" total-bufs total-bufs))
- forms-and-bufs)))
-
-(defun elisp-refs--search (symbol description match-fn &optional path-prefix)
- "Find references to SYMBOL in all loaded files; call MATCH-FN on each buffer.
-When PATH-PREFIX, limit to loaded files whose path starts with that prefix.
-
-Display the results in a hyperlinked buffer.
-
-MATCH-FN should return a list where each element takes the form:
-\(form start-pos end-pos)."
- (let* ((loaded-paths (elisp-refs--loaded-paths))
- (matching-paths (if path-prefix
- (--filter (s-starts-with? path-prefix it) loaded-paths)
- loaded-paths))
- (loaded-src-bufs (mapcar #'elisp-refs--contents-buffer matching-paths)))
- ;; Use unwind-protect to ensure we always cleanup temporary
- ;; buffers, even if the user hits C-g.
- (unwind-protect
- (progn
- (let ((forms-and-bufs
- (elisp-refs--search-1 loaded-src-bufs match-fn)))
- (elisp-refs--show-results symbol description forms-and-bufs
- (length loaded-src-bufs) path-prefix)))
- ;; Clean up temporary buffers.
- (--each loaded-src-bufs (kill-buffer it)))))
-
-(defun elisp-refs--completing-read-symbol (prompt &optional filter)
- "Read an interned symbol from the minibuffer,
-defaulting to the symbol at point. PROMPT is the string to prompt
-with.
-
-If FILTER is given, only offer symbols where (FILTER sym) returns
-t."
- (let ((filter (or filter (lambda (_) t))))
- (read
- (completing-read prompt
- (elisp-refs--filter-obarray filter)
- nil nil nil nil
- (-if-let (sym (thing-at-point 'symbol))
- (when (funcall filter (read sym))
- sym))))))
-
-;;; Commands
-
-;;;###autoload
-(defun elisp-refs-function (symbol &optional path-prefix)
- "Display all the references to function SYMBOL, in all loaded
-elisp files.
-
-If called with a prefix, prompt for a directory to limit the search.
-
-This searches for functions, not macros. For that, see
-`elisp-refs-macro'."
- (interactive
- (list (elisp-refs--completing-read-symbol "Function: " #'functionp)
- (when current-prefix-arg
- (read-directory-name "Limit search to loaded files in: "))))
- (when (not (functionp symbol))
- (if (macrop symbol)
- (user-error "%s is a macro. Did you mean elisp-refs-macro?"
- symbol)
- (user-error "%s is not a function. Did you mean elisp-refs-symbol?"
- symbol)))
- (elisp-refs--search symbol
- (elisp-refs--describe-button symbol 'function)
- (lambda (buf)
- (elisp-refs--read-and-find buf symbol #'elisp-refs--function-p))
- path-prefix))
-
-;;;###autoload
-(defun elisp-refs-macro (symbol &optional path-prefix)
- "Display all the references to macro SYMBOL, in all loaded
-elisp files.
-
-If called with a prefix, prompt for a directory to limit the search.
-
-This searches for macros, not functions. For that, see
-`elisp-refs-function'."
- (interactive
- (list (elisp-refs--completing-read-symbol "Macro: " #'macrop)
- (when current-prefix-arg
- (read-directory-name "Limit search to loaded files in: "))))
- (when (not (macrop symbol))
- (if (functionp symbol)
- (user-error "%s is a function. Did you mean elisp-refs-function?"
- symbol)
- (user-error "%s is not a function. Did you mean elisp-refs-symbol?"
- symbol)))
- (elisp-refs--search symbol
- (elisp-refs--describe-button symbol 'macro)
- (lambda (buf)
- (elisp-refs--read-and-find buf symbol #'elisp-refs--macro-p))
- path-prefix))
-
-;;;###autoload
-(defun elisp-refs-special (symbol &optional path-prefix)
- "Display all the references to special form SYMBOL, in all loaded
-elisp files.
-
-If called with a prefix, prompt for a directory to limit the search."
- (interactive
- (list (elisp-refs--completing-read-symbol "Special form: " #'special-form-p)
- (when current-prefix-arg
- (read-directory-name "Limit search to loaded files in: "))))
- (elisp-refs--search symbol
- (elisp-refs--describe-button symbol 'special-form)
- (lambda (buf)
- (elisp-refs--read-and-find buf symbol #'elisp-refs--special-p))
- path-prefix))
-
-;;;###autoload
-(defun elisp-refs-variable (symbol &optional path-prefix)
- "Display all the references to variable SYMBOL, in all loaded
-elisp files.
-
-If called with a prefix, prompt for a directory to limit the search."
- (interactive
- ;; This is awkward. We don't want to just offer defvar variables,
- ;; because then we can't search for code which uses `let' to bind
- ;; symbols. There doesn't seem to be a good way to only offer
- ;; variables that have been bound at some point.
- (list (elisp-refs--completing-read-symbol "Variable: " )
- (when current-prefix-arg
- (read-directory-name "Limit search to loaded files in: "))))
- (elisp-refs--search symbol
- (elisp-refs--describe-button symbol 'variable)
- (lambda (buf)
- (elisp-refs--read-and-find buf symbol #'elisp-refs--variable-p))
- path-prefix))
-
-;;;###autoload
-(defun elisp-refs-symbol (symbol &optional path-prefix)
- "Display all the references to SYMBOL in all loaded elisp files.
-
-If called with a prefix, prompt for a directory to limit the
-search."
- (interactive
- (list (elisp-refs--completing-read-symbol "Symbol: " )
- (when current-prefix-arg
- (read-directory-name "Limit search to loaded files in: "))))
- (elisp-refs--search symbol
- (elisp-refs--describe-button symbol 'symbol)
- (lambda (buf)
- (elisp-refs--read-and-find-symbol buf symbol))
- path-prefix))
-
-;;; Mode
-
-(defvar elisp-refs-mode-map
- (let ((map (make-sparse-keymap)))
- ;; TODO: it would be nice for TAB to navigate to file buttons too,
- ;; like *Help* does.
- (set-keymap-parent map special-mode-map)
- (define-key map (kbd "<tab>") #'elisp-refs-next-match)
- (define-key map (kbd "<backtab>") #'elisp-refs-prev-match)
- (define-key map (kbd "n") #'elisp-refs-next-match)
- (define-key map (kbd "p") #'elisp-refs-prev-match)
- (define-key map (kbd "RET") #'elisp-refs-visit-match)
- map)
- "Keymap for `elisp-refs-mode'.")
-
-(define-derived-mode elisp-refs-mode special-mode "Refs"
- "Major mode for refs results buffers.")
-
-(defun elisp--refs-visit-match (open-fn)
- "Go to the search result at point.
-Open file with function OPEN_FN. `find-file` or `find-file-other-window`"
- (interactive)
- (let* ((path (get-text-property (point) 'elisp-refs-path))
- (pos (get-text-property (point) 'elisp-refs-start-pos))
- (unindent (get-text-property (point) 'elisp-refs-unindented))
- (column-offset (current-column))
- (line-offset -1))
- (when (null path)
- (user-error "No match here"))
-
- ;; If point is not on the first line of the match, work out how
- ;; far away the first line is.
- (save-excursion
- (while (equal pos (get-text-property (point) 'elisp-refs-start-pos))
- (forward-line -1)
- (cl-incf line-offset)))
-
- (funcall open-fn path)
- (goto-char pos)
- ;; Move point so we're on the same char in the buffer that we were
- ;; on in the results buffer.
- (forward-line line-offset)
- (beginning-of-line)
- (let ((target-offset (+ column-offset unindent))
- (i 0))
- (while (< i target-offset)
- (if (looking-at "\t")
- (cl-incf i tab-width)
- (cl-incf i))
- (forward-char 1)))))
-
-(defun elisp-refs-visit-match ()
- "Goto the search result at point."
- (interactive)
- (elisp--refs-visit-match #'find-file))
-
-(defun elisp-refs-visit-match-other-window ()
- "Goto the search result at point, opening in another window."
- (interactive)
- (elisp--refs-visit-match #'find-file-other-window))
-
-
-(defun elisp-refs--move-to-match (direction)
- "Move point one match forwards.
-If DIRECTION is -1, moves backwards instead."
- (let* ((start-pos (point))
- (match-pos (get-text-property start-pos 'elisp-refs-start-pos))
- current-match-pos)
- (condition-case _err
- (progn
- ;; Move forward/backwards until we're on the next/previous match.
- (catch 'done
- (while t
- (setq current-match-pos
- (get-text-property (point) 'elisp-refs-start-pos))
- (when (and current-match-pos
- (not (equal match-pos current-match-pos)))
- (throw 'done nil))
- (forward-char direction)))
- ;; Move to the beginning of that match.
- (while (equal (get-text-property (point) 'elisp-refs-start-pos)
- (get-text-property (1- (point)) 'elisp-refs-start-pos))
- (forward-char -1))
- ;; Move forward until we're on the first char of match within that
- ;; line.
- (while (or
- (looking-at " ")
- (eq (get-text-property (point) 'face)
- 'font-lock-comment-face))
- (forward-char 1)))
- ;; If we're at the last result, don't move point.
- (end-of-buffer
- (progn
- (goto-char start-pos)
- (signal 'end-of-buffer nil))))))
-
-(defun elisp-refs-prev-match ()
- "Move to the previous search result in the Refs buffer."
- (interactive)
- (elisp-refs--move-to-match -1))
-
-(defun elisp-refs-next-match ()
- "Move to the next search result in the Refs buffer."
- (interactive)
- (elisp-refs--move-to-match 1))
-
-(provide 'elisp-refs)
-;;; elisp-refs.el ends here