diff options
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | helpful.el | 60 | ||||
| -rw-r--r-- | test/helpful-unit-test.el | 7 |
3 files changed, 69 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6a542..3188861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Fixed an issue when viewing `inhibit-read-only`. Improved the buffer prompt to be more relevant when inspecting buffer-local values. +Better source detection for functions defined by `defstruct` or other +macros. + # v0.15 Fixed a crash on formatting values. @@ -1206,6 +1206,59 @@ If .elc files exist without the corresponding .el, return nil." (find-library-name library-name) (error nil))) +(defun helpful--macroexpand-try (form) + "Try to fully macroexpand FORM. +If it fails, attempt to partially macroexpand FORM." + (catch 'result + (ignore-errors + ;; Happy path: we can fully expand the form. + (throw 'result (macroexpand-all form))) + (ignore-errors + ;; Attempt one level of macroexpansion. + (throw 'result (macroexpand-1 form))) + ;; Fallback: just return the original form. + form)) + +(defun helpful--tree-any-p (pred tree) + "Walk TREE, applying PRED to every subtree. +Return t if PRED ever returns t." + (cond + ((null tree) nil) + ((funcall pred tree) t) + ((not (consp tree)) nil) + (t (or + (helpful--tree-any-p pred (car tree)) + (helpful--tree-any-p pred (cdr tree)))))) + +(defun helpful--find-by-macroexpanding (buf sym callable-p) + "Search BUF for the definition of SYM by macroexpanding +interesting forms in BUF." + (catch 'found + (with-current-buffer buf + (save-excursion + (goto-char (point-min)) + (condition-case nil + (while t + (let ((form (read (current-buffer))) + (var-def-p + (lambda (sexp) + (and (eq (car-safe sexp) 'defvar) + (eq (car-safe (cdr sexp)) sym)))) + (fn-def-p + (lambda (sexp) + ;; `defun' ultimately expands to `defalias'. + (and (eq (car-safe sexp) 'defalias) + (equal (car-safe (cdr sexp)) `(quote ,sym)))))) + (setq form (helpful--macroexpand-try form)) + + (when (helpful--tree-any-p + (if callable-p fn-def-p var-def-p) + form) + ;; `read' puts point at the end of the form, so go + ;; back to the start. + (throw 'found (scan-sexps (point) -1))))) + (end-of-file nil)))))) + (defun helpful--definition (sym callable-p) "Return a list (BUF POS OPENED) where SYM is defined. @@ -1271,7 +1324,12 @@ buffer." (save-restriction (widen) (setq pos - (cdr (find-function-search-for-symbol sym nil library-name)))))))) + (cdr (find-function-search-for-symbol sym nil library-name)))))) + ;; If we found the containing buffer, but not the symbol, attempt + ;; to find it by macroexpanding interesting forms. + (when (and buf (not pos)) + (setq pos (helpful--find-by-macroexpanding buf sym t))))) + ;; A function, but no file found. (callable-p ;; Functions defined interactively may have an edebug property ;; that contains the location of the definition. diff --git a/test/helpful-unit-test.el b/test/helpful-unit-test.el index 756bc76..541639c 100644 --- a/test/helpful-unit-test.el +++ b/test/helpful-unit-test.el @@ -352,6 +352,13 @@ variables defined without `defvar'." (-let [(buf pos opened) (helpful--definition 'test-foo-edebug-defn t)] (should buf)))))) +(ert-deftest helpful--definition-defstruct () + "Ensure we find the position of struct functions." + (-let [(buf pos _) + (helpful--definition #'make-ert-test t)] + (should buf) + (should pos))) + (ert-deftest helpful-variable () "Smoke test for `helpful-variable'." (helpful-variable 'tab-width)) |
