helpful.el (107069B)
1 ;;; helpful.el --- A better *help* buffer -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2017-2022 Wilfred Hughes 4 5 ;; Author: Wilfred Hughes <me@wilfred.me.uk> 6 ;; URL: https://github.com/Wilfred/helpful 7 ;; Keywords: help, lisp 8 ;; Version: 0.21 9 ;; Package-Requires: ((emacs "25") (dash "2.18.0") (s "1.11.0") (f "0.20.0") (elisp-refs "1.2")) 10 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;; Helpful is a replacement for *help* buffers that provides much more 27 ;; contextual information. To get started, try: 28 ;; `M-x helpful-function RET helpful-function 29 ;; 30 ;; The full set of commands you can try is: 31 ;; 32 ;; * helpful-function 33 ;; * helpful-command 34 ;; * helpful-key 35 ;; * helpful-macro 36 ;; * helpful-callable 37 ;; * helpful-variable 38 ;; * helpful-at-point 39 ;; 40 ;; For more information and screenshots, see 41 ;; https://github.com/wilfred/helpful 42 43 ;;; Code: 44 45 (require 'elisp-refs) 46 (require 'help) 47 (require 'help-fns) 48 (require 'dash) 49 (require 's) 50 (require 'f) 51 (require 'find-func) 52 (require 'nadvice) 53 (require 'info-look) 54 (require 'edebug) 55 (require 'trace) 56 (require 'imenu) 57 (require 'cc-langs) 58 59 (declare-function org-link-types "ol" ()) 60 (declare-function org-link-store-props "ol" (&rest plist)) 61 (declare-function org-link-get-parameter "ol" (type key)) 62 63 (defvar-local helpful--sym nil) 64 (defvar-local helpful--callable-p nil) 65 (defvar-local helpful--associated-buffer nil 66 "The buffer being used when showing inspecting 67 buffer-local variables.") 68 (defvar-local helpful--start-buffer nil 69 "The buffer we were originally called from.") 70 (defvar-local helpful--view-literal nil 71 "Whether to show a value as a literal, or a pretty interactive 72 view.") 73 (defvar-local helpful--first-display t 74 "Whether this is the first time this results buffer has been 75 displayed. 76 77 Nil means that we're refreshing, so we don't want to clobber any 78 settings changed by the user.") 79 80 (defgroup helpful nil 81 "A rich help system with contextual information." 82 :link '(url-link "https://github.com/Wilfred/helpful") 83 :group 'help) 84 85 (defcustom helpful-max-buffers 86 5 87 "Helpful will kill the least recently used Helpful buffer 88 if there are more than this many. 89 90 To disable cleanup entirely, set this variable to nil. See also 91 `helpful-kill-buffers' for a one-off cleanup." 92 :type '(choice (const nil) number) 93 :group 'helpful) 94 95 (defcustom helpful-switch-buffer-function 96 #'pop-to-buffer 97 "Function called to display the *Helpful* buffer." 98 :type 'function 99 :group 'helpful) 100 101 ;; TODO: explore whether more basic highlighting is fast enough to 102 ;; handle larger functions. See `c-font-lock-init' and its use of 103 ;; font-lock-keywords-1. 104 (defconst helpful-max-highlight 5000 105 "Don't highlight code with more than this many characters. 106 107 This is currently only used for C code, as lisp highlighting 108 seems to be more efficient. This may change again in future. 109 110 See `this-command' as an example of a large piece of C code that 111 can make Helpful very slow.") 112 113 (defun helpful--kind-name (symbol callable-p) 114 "Describe what kind of symbol this is." 115 (cond 116 ((not callable-p) "variable") 117 ((commandp symbol) "command") 118 ((macrop symbol) "macro") 119 ((functionp symbol) "function") 120 ((special-form-p symbol) "special form"))) 121 122 (defun helpful--buffer (symbol callable-p) 123 "Return a buffer to show help for SYMBOL in." 124 (let* ((current-buffer (current-buffer)) 125 (buf-name 126 (format "*helpful %s*" 127 (if (symbolp symbol) 128 (format "%s: %s" 129 (helpful--kind-name symbol callable-p) 130 symbol) 131 "lambda"))) 132 (buf (get-buffer buf-name))) 133 (unless buf 134 ;; If we need to create the buffer, ensure we don't exceed 135 ;; `helpful-max-buffers' by killing the least recently used. 136 (when (numberp helpful-max-buffers) 137 (let* ((buffers (buffer-list)) 138 (helpful-bufs (--filter (with-current-buffer it 139 (eq major-mode 'helpful-mode)) 140 buffers)) 141 ;; `buffer-list' seems to be ordered by most recently 142 ;; visited first, so keep those. 143 (excess-buffers (-drop (1- helpful-max-buffers) helpful-bufs))) 144 ;; Kill buffers so we have one buffer less than the maximum 145 ;; before we create a new one. 146 (-each excess-buffers #'kill-buffer))) 147 148 (setq buf (get-buffer-create buf-name))) 149 150 ;; Initialise the buffer with the symbol and associated data. 151 (with-current-buffer buf 152 (helpful-mode) 153 (setq helpful--sym symbol) 154 (setq helpful--callable-p callable-p) 155 (setq helpful--start-buffer current-buffer) 156 (setq helpful--associated-buffer current-buffer) 157 (setq list-buffers-directory 158 (if (symbolp symbol) (format "%s: %s" (helpful--kind-name symbol callable-p) symbol) "lambda")) 159 (if (helpful--primitive-p symbol callable-p) 160 (setq-local comment-start "//") 161 (setq-local comment-start ";"))) 162 buf)) 163 164 (defface helpful-heading 165 '((t (:weight bold))) 166 "Face used for headings in Helpful buffers.") 167 168 (defun helpful--heading (text) 169 "Propertize TEXT as a heading." 170 (propertize (concat text "\n") 'face 'helpful-heading)) 171 172 (defun helpful--format-closure (sym form) 173 "Given a closure, return an equivalent defun form." 174 (-let (((_keyword _env args . body) form) 175 (docstring nil)) 176 (when (stringp (car body)) 177 (setq docstring (car body)) 178 (setq body (cdr body)) 179 ;; Ensure that the docstring doesn't have lines starting with (, 180 ;; or it breaks indentation. 181 (setq docstring 182 (s-replace "\n(" "\n\\(" docstring))) 183 (if docstring 184 `(defun ,sym ,args ,docstring ,@body) 185 `(defun ,sym ,args ,@body)))) 186 187 (defun helpful--pretty-print (value) 188 "Pretty-print VALUE. 189 190 If VALUE is very big, the user may press \\[keyboard-quit] to 191 gracefully stop the printing. If VALUE is self-referential, the 192 error will be caught and displayed." 193 ;; Inspired by `ielm-eval-input'. 194 (condition-case err 195 (s-trim-right (pp-to-string value)) 196 (error 197 (propertize (format "(Display error: %s)" (cadr err)) 198 'face 'font-lock-comment-face)) 199 (quit 200 (propertize "(User quit during pretty-printing.)" 201 'face 'font-lock-comment-face)))) 202 203 (defun helpful--sort-symbols (sym-list) 204 "Sort symbols in SYM-LIST alphabetically." 205 (--sort 206 (string< (symbol-name it) (symbol-name other)) 207 sym-list)) 208 209 (defun helpful--button (text type &rest properties) 210 ;; `make-text-button' mutates our string to add properties. Copy 211 ;; TEXT to prevent mutating our arguments, and to support 'pure' 212 ;; strings, which are read-only. 213 (setq text (substring-no-properties text)) 214 (apply #'make-text-button 215 text nil 216 :type type 217 properties)) 218 219 (defun helpful--canonical-symbol (sym callable-p) 220 "If SYM is an alias, return the underlying symbol. 221 Return SYM otherwise." 222 (let ((depth 0)) 223 (if (and (symbolp sym) callable-p) 224 (progn 225 ;; Follow the chain of symbols until we find a symbol that 226 ;; isn't pointing to a symbol. 227 (while (and (symbolp (symbol-function sym)) 228 (< depth 10)) 229 (setq sym (symbol-function sym)) 230 (setq depth (1+ depth))) 231 ;; If this is an alias to a primitive, return the 232 ;; primitive's symbol. 233 (when (subrp (symbol-function sym)) 234 (setq sym (intern (subr-name (symbol-function sym)))))) 235 (setq sym (indirect-variable sym)))) 236 sym) 237 238 (defun helpful--aliases (sym callable-p) 239 "Return all the aliases for SYM." 240 (let ((canonical (helpful--canonical-symbol sym callable-p)) 241 aliases) 242 (mapatoms 243 (lambda (s) 244 (when (and 245 ;; Skip variables that aren't bound, so we're faster. 246 (if callable-p (fboundp s) (boundp s)) 247 248 ;; If this symbol is a new alias for our target sym, 249 ;; add it. 250 (eq canonical (helpful--canonical-symbol s callable-p)) 251 252 ;; Don't include SYM. 253 (not (eq sym s))) 254 (push s aliases)))) 255 (helpful--sort-symbols aliases))) 256 257 (defun helpful--obsolete-info (sym callable-p) 258 (when (symbolp sym) 259 (get sym (if callable-p 'byte-obsolete-info 'byte-obsolete-variable)))) 260 261 (defun helpful--format-alias (sym callable-p) 262 (let ((obsolete-info (helpful--obsolete-info sym callable-p)) 263 (sym-button (helpful--button 264 (symbol-name sym) 265 'helpful-describe-exactly-button 266 'symbol sym 267 'callable-p callable-p))) 268 (cond 269 (obsolete-info 270 (-if-let (version (-last-item obsolete-info)) 271 (format "%s (obsolete since %s)" sym-button version) 272 (format "%s (obsolete)" sym-button))) 273 (t 274 sym-button)))) 275 276 (defun helpful--indent-rigidly (s amount) 277 "Indent string S by adding AMOUNT spaces to each line." 278 (with-temp-buffer 279 (insert s) 280 (indent-rigidly (point-min) (point-max) amount) 281 (buffer-string))) 282 283 (defun helpful--format-properties (symbol) 284 "Return a string describing all the properties of SYMBOL." 285 (let* ((syms-and-vals 286 (-partition 2 (and (symbolp symbol) (symbol-plist symbol)))) 287 (syms-and-vals 288 (-sort (-lambda ((sym1 _) (sym2 _)) 289 (string-lessp (symbol-name sym1) (symbol-name sym2))) 290 syms-and-vals)) 291 (lines 292 (--map 293 (-let* (((sym val) it) 294 (pretty-val 295 (helpful--pretty-print val))) 296 (format "%s\n%s%s" 297 (propertize (symbol-name sym) 298 'face 'font-lock-constant-face) 299 (helpful--indent-rigidly pretty-val 2) 300 (cond 301 ;; Also offer to disassemble byte-code 302 ;; properties. 303 ((byte-code-function-p val) 304 (format "\n %s" 305 (helpful--make-disassemble-button val))) 306 ((eq sym 'ert--test) 307 (format "\n %s" 308 (helpful--make-run-test-button symbol))) 309 (t 310 "")))) 311 syms-and-vals))) 312 (when lines 313 (s-join "\n" lines)))) 314 315 (define-button-type 'helpful-forget-button 316 'action #'helpful--forget 317 'symbol nil 318 'callable-p nil 319 'follow-link t 320 'help-echo "Unbind this function") 321 322 ;; TODO: it would be nice to optionally delete the source code too. 323 (defun helpful--forget (button) 324 "Unbind the current symbol." 325 (let* ((sym (button-get button 'symbol)) 326 (callable-p (button-get button 'callable-p)) 327 (kind (helpful--kind-name sym callable-p))) 328 (when (yes-or-no-p (format "Forget %s %s?" kind sym)) 329 (if callable-p 330 (fmakunbound sym) 331 (makunbound sym)) 332 (message "Forgot %s %s." kind sym) 333 (kill-buffer (current-buffer))))) 334 335 (define-button-type 'helpful-c-source-directory 336 'action #'helpful--c-source-directory 337 'follow-link t 338 'help-echo "Set directory to Emacs C source code") 339 340 (defun helpful--c-source-directory (_button) 341 "Set `find-function-C-source-directory' so we can show the 342 source code to primitives." 343 (let ((emacs-src-dir (read-directory-name "Path to Emacs source code: "))) 344 ;; Let the user specify the source path with or without src/, 345 ;; which is a subdirectory in the Emacs tree. 346 (unless (equal (f-filename emacs-src-dir) "src") 347 (setq emacs-src-dir (f-join emacs-src-dir "src"))) 348 (setq find-function-C-source-directory emacs-src-dir)) 349 (helpful-update)) 350 351 (define-button-type 'helpful-disassemble-button 352 'action #'helpful--disassemble 353 'follow-link t 354 'object nil 355 'help-echo "Show disassembled bytecode") 356 357 (defun helpful--disassemble (button) 358 "Disassemble the current symbol." 359 ;; `disassemble' can handle both symbols (e.g. 'when) and raw 360 ;; byte-code objects. 361 (disassemble (button-get button 'object))) 362 363 (define-button-type 'helpful-run-test-button 364 'action #'helpful--run-test 365 'follow-link t 366 'symbol nil 367 'help-echo "Run ERT test") 368 369 (defun helpful--run-test (button) 370 "Disassemble the current symbol." 371 (ert (button-get button 'symbol))) 372 373 (define-button-type 'helpful-edebug-button 374 'action #'helpful--edebug 375 'follow-link t 376 'symbol nil 377 'help-echo "Toggle edebug (re-evaluates definition)") 378 379 (defun helpful--kbd-macro-p (sym) 380 "Is SYM a keyboard macro?" 381 (and (symbolp sym) 382 (let ((func (symbol-function sym))) 383 (or (stringp func) 384 (vectorp func))))) 385 386 (defun helpful--edebug-p (sym) 387 "Does function SYM have its definition patched by edebug?" 388 (let ((fn-def (indirect-function sym))) 389 ;; Edebug replaces function source code with a sexp that has 390 ;; `edebug-enter', `edebug-after' etc interleaved. This means the 391 ;; function is interpreted, so `indirect-function' returns a list. 392 (when (and (consp fn-def) (consp (cdr fn-def))) 393 (-let [fn-end (-last-item fn-def)] 394 (and (consp fn-end) 395 (eq (car fn-end) 'edebug-enter)))))) 396 397 (defun helpful--can-edebug-p (sym callable-p buf pos) 398 "Can we use edebug with SYM?" 399 (and 400 ;; SYM must be a function. 401 callable-p 402 ;; The function cannot be a primitive, it must be defined in elisp. 403 (not (helpful--primitive-p sym callable-p)) 404 ;; We need to be able to find its definition, or we can't step 405 ;; through the source. 406 buf pos)) 407 408 (defun helpful--toggle-edebug (sym) 409 "Enable edebug when function SYM is called, 410 or disable if already enabled." 411 (-let ((should-edebug (not (helpful--edebug-p sym))) 412 ((buf pos created) (helpful--definition sym t))) 413 (if (and buf pos) 414 (progn 415 (with-current-buffer buf 416 (save-excursion 417 (save-restriction 418 (widen) 419 (goto-char pos) 420 421 (let* ((edebug-all-forms should-edebug) 422 (edebug-all-defs should-edebug) 423 (form (edebug-read-top-level-form))) 424 ;; Based on `edebug-eval-defun'. 425 (eval (eval-sexp-add-defvars form) lexical-binding))))) 426 ;; If we're enabling edebug, we need the source buffer to 427 ;; exist. Otherwise, we can clean it up. 428 (when (and created (not should-edebug)) 429 (kill-buffer buf))) 430 431 (user-error "Could not find source for edebug")))) 432 433 (defun helpful--edebug (button) 434 "Toggle edebug for the current symbol." 435 (helpful--toggle-edebug (button-get button 'symbol)) 436 (helpful-update)) 437 438 (define-button-type 'helpful-trace-button 439 'action #'helpful--trace 440 'follow-link t 441 'symbol nil 442 'help-echo "Toggle function tracing") 443 444 (defun helpful--trace (button) 445 "Toggle tracing for the current symbol." 446 (let ((sym (button-get button 'symbol))) 447 (if (trace-is-traced sym) 448 (untrace-function sym) 449 (trace-function sym))) 450 (helpful-update)) 451 452 (define-button-type 'helpful-navigate-button 453 'action #'helpful--navigate 454 'path nil 455 'position nil 456 'follow-link t 457 'help-echo "Navigate to definition") 458 459 (defun helpful--goto-char-widen (pos) 460 "Move point to POS in the current buffer. 461 If narrowing is in effect, widen if POS isn't in the narrowed area." 462 (when (or (< pos (point-min)) 463 (> pos (point-max))) 464 (widen)) 465 (goto-char pos)) 466 467 (defun helpful--navigate (button) 468 "Navigate to the path this BUTTON represents." 469 (find-file (substring-no-properties (button-get button 'path))) 470 ;; We use `get-text-property' to work around an Emacs 25 bug: 471 ;; http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=f7c4bad17d83297ee9a1b57552b1944020f23aea 472 (-when-let (pos (get-text-property button 'position 473 (marker-buffer button))) 474 (helpful--goto-char-widen pos))) 475 476 (defun helpful--navigate-button (text path &optional pos) 477 "Return a button that opens PATH and puts point at POS." 478 (helpful--button 479 text 480 'helpful-navigate-button 481 'path path 482 'position pos)) 483 484 (define-button-type 'helpful-buffer-button 485 'action #'helpful--switch-to-buffer 486 'buffer nil 487 'position nil 488 'follow-link t 489 'help-echo "Switch to this buffer") 490 491 (defun helpful--switch-to-buffer (button) 492 "Navigate to the buffer this BUTTON represents." 493 (let ((buf (button-get button 'buffer)) 494 (pos (button-get button 'position))) 495 (switch-to-buffer buf) 496 (when pos 497 (helpful--goto-char-widen pos)))) 498 499 (defun helpful--buffer-button (buffer &optional pos) 500 "Return a button that switches to BUFFER and puts point at POS." 501 (helpful--button 502 (buffer-name buffer) 503 'helpful-buffer-button 504 'buffer buffer 505 'position pos)) 506 507 (define-button-type 'helpful-customize-button 508 'action #'helpful--customize 509 'symbol nil 510 'follow-link t 511 'help-echo "Open Customize for this symbol") 512 513 (defun helpful--customize (button) 514 "Open Customize for this symbol." 515 (customize-variable (button-get button 'symbol))) 516 517 (define-button-type 'helpful-associated-buffer-button 518 'action #'helpful--associated-buffer 519 'symbol nil 520 'prompt-p nil 521 'follow-link t 522 'help-echo "Change associated buffer") 523 524 (defun helpful--read-live-buffer (prompt predicate) 525 "Read a live buffer name, and return the buffer object. 526 527 This is largely equivalent to `read-buffer', but counsel.el 528 overrides that to include previously opened buffers." 529 (let* ((names (-map #'buffer-name (buffer-list))) 530 (default 531 (cond 532 ;; If we're already looking at a buffer-local value, start 533 ;; the prompt from the relevant buffer. 534 ((and helpful--associated-buffer 535 (buffer-live-p helpful--associated-buffer)) 536 (buffer-name helpful--associated-buffer)) 537 ;; If we're looking at the global value, offer the initial 538 ;; buffer. 539 ((and helpful--start-buffer 540 (buffer-live-p helpful--start-buffer)) 541 (buffer-name helpful--start-buffer)) 542 ;; If we're looking at the global value and have no initial 543 ;; buffer, choose the first normal buffer. 544 (t 545 (--first (and (not (s-starts-with-p " " it)) 546 (not (s-starts-with-p "*" it))) 547 names)) 548 ))) 549 (get-buffer 550 (completing-read 551 prompt 552 names 553 predicate 554 t 555 nil 556 nil 557 default)))) 558 559 (defun helpful--associated-buffer (button) 560 "Change the associated buffer, so we can see buffer-local values." 561 (let ((sym (button-get button 'symbol)) 562 (prompt-p (button-get button 'prompt-p))) 563 (if prompt-p 564 (setq helpful--associated-buffer 565 (helpful--read-live-buffer 566 "View variable in: " 567 (lambda (buf-name) 568 (local-variable-p sym (get-buffer buf-name))))) 569 (setq helpful--associated-buffer nil))) 570 (helpful-update)) 571 572 (define-button-type 'helpful-toggle-button 573 'action #'helpful--toggle 574 'symbol nil 575 'buffer nil 576 'follow-link t 577 'help-echo "Toggle this symbol between t and nil") 578 579 (defun helpful--toggle (button) 580 "Toggle the symbol between nil and t." 581 (let ((sym (button-get button 'symbol)) 582 (buf (button-get button 'buffer))) 583 (save-current-buffer 584 ;; If this is a buffer-local variable, ensure we're in the right 585 ;; buffer. 586 (when buf 587 (set-buffer buf)) 588 (set sym (not (symbol-value sym)))) 589 (helpful-update))) 590 591 (define-button-type 'helpful-set-button 592 'action #'helpful--set 593 'symbol nil 594 'buffer nil 595 'follow-link t 596 'help-echo "Set the value of this symbol") 597 598 (defun helpful--set (button) 599 "Set the value of this symbol." 600 (let* ((sym (button-get button 'symbol)) 601 (buf (button-get button 'buffer)) 602 (sym-value (helpful--sym-value sym buf)) 603 ;; Inspired by `counsel-read-setq-expression'. 604 (expr 605 (minibuffer-with-setup-hook 606 (lambda () 607 (add-function :before-until (local 'eldoc-documentation-function) 608 #'elisp-eldoc-documentation-function) 609 (run-hooks 'eval-expression-minibuffer-setup-hook) 610 (goto-char (minibuffer-prompt-end)) 611 (forward-char (length (format "(setq %S " sym)))) 612 (read-from-minibuffer 613 "Eval: " 614 (format 615 (if (or (consp sym-value) 616 (and (symbolp sym-value) 617 (not (null sym-value)) 618 (not (keywordp sym-value)))) 619 "(setq %s '%S)" 620 "(setq %s %S)") 621 sym sym-value) 622 read-expression-map t 623 'read-expression-history)))) 624 (save-current-buffer 625 ;; If this is a buffer-local variable, ensure we're in the right 626 ;; buffer. 627 (when buf 628 (set-buffer buf)) 629 (eval-expression expr)) 630 (helpful-update))) 631 632 (define-button-type 'helpful-view-literal-button 633 'action #'helpful--view-literal 634 'help-echo "Toggle viewing as a literal") 635 636 (defun helpful--view-literal (_button) 637 "Set the value of this symbol." 638 (setq helpful--view-literal 639 (not helpful--view-literal)) 640 (helpful-update)) 641 642 (define-button-type 'helpful-all-references-button 643 'action #'helpful--all-references 644 'symbol nil 645 'callable-p nil 646 'follow-link t 647 'help-echo "Find all references to this symbol") 648 649 (defun helpful--all-references (button) 650 "Find all the references to the symbol that this BUTTON represents." 651 (let ((sym (button-get button 'symbol)) 652 (callable-p (button-get button 'callable-p))) 653 (cond 654 ((not callable-p) 655 (elisp-refs-variable sym)) 656 ((functionp sym) 657 (elisp-refs-function sym)) 658 ((macrop sym) 659 (elisp-refs-macro sym))))) 660 661 (define-button-type 'helpful-callees-button 662 'action #'helpful--show-callees 663 'symbol nil 664 'source nil 665 'follow-link t 666 'help-echo "Find the functions called by this function/macro") 667 668 (defun helpful--display-callee-group (callees) 669 "Insert every entry in CALLEES." 670 (dolist (sym (helpful--sort-symbols callees)) 671 (insert " " 672 (helpful--button 673 (symbol-name sym) 674 'helpful-describe-exactly-button 675 'symbol sym 676 'callable-p t) 677 "\n"))) 678 679 (defun helpful--show-callees (button) 680 "Find all the references to the symbol that this BUTTON represents." 681 (let* ((buf (get-buffer-create "*helpful callees*")) 682 (sym (button-get button 'symbol)) 683 (raw-source (button-get button 'source)) 684 (source 685 (if (stringp raw-source) 686 (read raw-source) 687 raw-source)) 688 (syms (helpful--callees source)) 689 (primitives (-filter (lambda (sym) (helpful--primitive-p sym t)) syms)) 690 (compounds (-remove (lambda (sym) (helpful--primitive-p sym t)) syms))) 691 692 (pop-to-buffer buf) 693 (let ((inhibit-read-only t)) 694 (erase-buffer) 695 696 ;; TODO: Macros used, special forms used, global vars used. 697 (insert (format "Functions called by %s:\n\n" sym)) 698 (helpful--display-callee-group compounds) 699 700 (when primitives 701 (insert "\n") 702 (insert (format "Primitives called by %s:\n\n" sym)) 703 (helpful--display-callee-group primitives)) 704 705 (goto-char (point-min)) 706 707 (helpful-mode)))) 708 709 (define-button-type 'helpful-manual-button 710 'action #'helpful--manual 711 'symbol nil 712 'follow-link t 713 'help-echo "View this symbol in the Emacs manual") 714 715 (defun helpful--manual (button) 716 "Open the manual for the system that this BUTTON represents." 717 (let ((sym (button-get button 'symbol))) 718 (info-lookup 'symbol sym #'emacs-lisp-mode))) 719 720 (define-button-type 'helpful-describe-button 721 'action #'helpful--describe 722 'symbol nil 723 'follow-link t 724 'help-echo "Describe this symbol") 725 726 (defun helpful--describe (button) 727 "Describe the symbol that this BUTTON represents." 728 (let ((sym (button-get button 'symbol))) 729 (helpful-symbol sym))) 730 731 (define-button-type 'helpful-describe-exactly-button 732 'action #'helpful--describe-exactly 733 'symbol nil 734 'callable-p nil 735 'follow-link t 736 'help-echo "Describe this symbol") 737 738 (defun helpful--describe-exactly (button) 739 "Describe the symbol that this BUTTON represents. 740 This differs from `helpful--describe' because here we know 741 whether the symbol represents a variable or a callable." 742 (let ((sym (button-get button 'symbol)) 743 (callable-p (button-get button 'callable-p))) 744 (if callable-p 745 (helpful-callable sym) 746 (helpful-variable sym)))) 747 748 (define-button-type 'helpful-info-button 749 'action #'helpful--info 750 'info-node nil 751 'follow-link t 752 'help-echo "View this Info node") 753 754 (defun helpful--info (button) 755 "Describe the symbol that this BUTTON represents." 756 (info (button-get button 'info-node))) 757 758 (define-button-type 'helpful-shortdoc-button 759 'action #'helpful--shortdoc 760 'info-node nil 761 'follow-link t 762 'help-echo "View this Shortdoc group") 763 764 (defun helpful--shortdoc (button) 765 "Describe the symbol that this BUTTON represents." 766 (shortdoc-display-group (button-get button 'shortdoc-group) 767 (button-get button 'symbol))) 768 769 (defun helpful--split-first-line (docstring) 770 "If the first line is a standalone sentence, ensure we have a 771 blank line afterwards." 772 (let* ((lines (s-lines docstring)) 773 (first-line (-first-item lines)) 774 (second-line (when (> (length lines) 1) (nth 1 lines)))) 775 (if (and (s-ends-with-p "." first-line) 776 (stringp second-line) 777 (not (equal second-line ""))) 778 (s-join "\n" 779 (-cons* first-line "" (cdr lines))) 780 docstring))) 781 782 (defun helpful--propertize-sym-ref (sym-name before-txt after-txt) 783 "Given a symbol name from a docstring, convert to a button (if 784 bound) or else highlight." 785 (let* ((sym (intern sym-name))) 786 (cond 787 ;; Highlight keywords. 788 ((s-matches-p 789 (rx ":" 790 symbol-start 791 (+? (or (syntax word) (syntax symbol))) 792 symbol-end) 793 sym-name) 794 (propertize sym-name 795 'face 'font-lock-builtin-face)) 796 ((and (boundp sym) (s-ends-with-p "variable " before-txt)) 797 (helpful--button 798 sym-name 799 'helpful-describe-exactly-button 800 'symbol sym 801 'callable-p nil)) 802 ((and (fboundp sym) (or 803 (s-starts-with-p " command" after-txt) 804 (s-ends-with-p "command " before-txt) 805 (s-ends-with-p "function " before-txt))) 806 (helpful--button 807 sym-name 808 'helpful-describe-exactly-button 809 'symbol sym 810 'callable-p t)) 811 ;; Only create a link if this is a symbol that is bound as a 812 ;; variable or callable. 813 ((or (boundp sym) (fboundp sym)) 814 (helpful--button 815 sym-name 816 'helpful-describe-button 817 'symbol sym)) 818 ;; If this is already a button, don't modify it. 819 ((get-text-property 0 'button sym-name) 820 sym-name) 821 ;; Highlight the quoted string. 822 (t 823 (propertize sym-name 824 'face 'font-lock-constant-face))))) 825 826 (defun helpful--propertize-info (docstring) 827 "Convert info references in DOCSTRING to buttons." 828 (replace-regexp-in-string 829 ;; Replace all text that looks like a link to an Info page. 830 (rx (seq (group 831 bow 832 (any "Ii") 833 "nfo" 834 (one-or-more whitespace)) 835 (group 836 (or "node" "anchor") 837 (one-or-more whitespace)) 838 (any "'`‘") 839 (group 840 (one-or-more 841 (not (any "'’")))) 842 (any "'’"))) 843 (lambda (it) 844 ;; info-name matches "[Ii]nfo ". 845 ;; space matches "node " or "anchor ". 846 ;; info-node has the form "(cl)Loop Facility". 847 (let ((info-name (match-string 1 it)) 848 (space (match-string 2 it)) 849 (info-node (match-string 3 it))) 850 ;; If the docstring doesn't specify a manual, assume the Emacs manual. 851 (save-match-data 852 (unless (string-match "^([^)]+)" info-node) 853 (setq info-node (concat "(emacs)" info-node)))) 854 (concat 855 info-name 856 space 857 (helpful--button 858 info-node 859 'helpful-info-button 860 'info-node info-node)))) 861 docstring 862 t t)) 863 864 (defun helpful--keymap-keys (keymap) 865 "Return all the keys and commands in KEYMAP. 866 Flattens nested keymaps and follows remapped commands. 867 868 Returns a list of pairs (KEYCODES COMMAND), where KEYCODES is a 869 vector suitable for `key-description', and COMMAND is a smbol." 870 (cond 871 ;; Prefix keys. 872 ((and 873 (symbolp keymap) 874 (fboundp keymap) 875 ;; Prefix keys use a keymap in the function slot of a symbol. 876 (keymapp (symbol-function keymap))) 877 (helpful--keymap-keys (symbol-function keymap))) 878 ;; Other symbols or compiled functions mean we've reached a leaf, 879 ;; so this is a command we can call. 880 ((or 881 (symbolp keymap) 882 (functionp keymap) 883 ;; Strings or vectors mean a keyboard macro. 884 (stringp keymap) 885 (vectorp keymap)) 886 `(([] ,keymap))) 887 ((stringp (car keymap)) 888 (helpful--keymap-keys (cdr keymap))) 889 ;; Otherwise, recurse on the keys at this level of the keymap. 890 (t 891 (let (result) 892 (dolist (item (cdr keymap)) 893 (cond 894 ((and (consp item) 895 (eq (car item) 'menu-bar)) 896 ;; Skip menu bar items. 897 nil) 898 ;; Sparse keymaps are lists. 899 ((consp item) 900 (-let [(keycode . value) item] 901 (-each (helpful--keymap-keys value) 902 (-lambda ((keycodes command)) 903 (push (list (vconcat (vector keycode) keycodes) command) 904 result))))) 905 ;; Dense keymaps are char-tables. 906 ((char-table-p item) 907 (map-char-table 908 (lambda (keycode value) 909 (-each (helpful--keymap-keys value) 910 (-lambda ((keycodes command)) 911 (push (list (vconcat (vector keycode) keycodes) command) 912 result)))) 913 item)))) 914 ;; For every command `new-func' mapped to a command `orig-func', show `new-func' with 915 ;; the key sequence for `orig-func'. 916 (setq result 917 (-map-when 918 (-lambda ((keycodes _)) 919 (and (> (length keycodes) 1) 920 (eq (elt keycodes 0) 'remap))) 921 (-lambda ((keycodes command)) 922 (list 923 (where-is-internal (elt keycodes 1) global-map t) 924 command)) 925 result)) 926 ;; Preserve the original order of the keymap. 927 (nreverse result))))) 928 929 (defun helpful--format-hook (hook-val) 930 "Given a list value assigned to a hook, format it with links to functions." 931 (let ((lines 932 (--map 933 (if (and (symbolp it) (fboundp it)) 934 (helpful--button 935 (symbol-name it) 936 'helpful-describe-exactly-button 937 'symbol it 938 'callable-p t) 939 (helpful--syntax-highlight (helpful--pretty-print it))) 940 hook-val))) 941 (format "(%s)" 942 (s-join "\n " lines)))) 943 944 ;; TODO: unlike `substitute-command-keys', this shows keybindings 945 ;; which are currently shadowed (e.g. a global minor mode map). 946 (defun helpful--format-keymap (keymap) 947 "Format KEYMAP." 948 (let* ((keys-and-commands (helpful--keymap-keys keymap)) 949 ;; Convert keycodes [27 i] to "C-M-i". 950 (keys (-map #'-first-item keys-and-commands)) 951 ;; Add padding so all our strings are the same length. 952 (formatted-keys (-map #'key-description keys)) 953 (max-formatted-length (-max (cons 0 (-map #'length formatted-keys)))) 954 (aligned-keys (--map (s-pad-right (1+ max-formatted-length) 955 " " it) 956 formatted-keys)) 957 ;; Format commands as buttons. 958 (commands (-map (-lambda ((_ command)) command) 959 keys-and-commands)) 960 (formatted-commands 961 (--map 962 (cond 963 ((symbolp it) 964 (helpful--button 965 (symbol-name it) 966 'helpful-describe-button 967 'symbol it)) 968 ((or (stringp it) (vectorp it)) 969 "Keyboard Macro") 970 (t 971 "#<anonymous-function>")) 972 commands)) 973 ;; Build lines for display. 974 (lines 975 (-map (-lambda ((key . command)) (format "%s %s" key command)) 976 (-zip-pair aligned-keys formatted-commands)))) 977 ;; The flattened keymap will have normal bindings first, and 978 ;; inherited bindings last. Sort so that we group by prefix. 979 (s-join "\n" (-sort #'string< lines)))) 980 981 (defun helpful--format-commands (str keymap) 982 "Replace all the \\[ references in STR with buttons." 983 (replace-regexp-in-string 984 ;; Text of the form \\[foo-command] 985 (rx "\\[" (group (+ (not (in "]")))) "]") 986 (lambda (it) 987 (let* ((button-face (if (>= emacs-major-version 28) 'help-key-binding 'button)) 988 (symbol-name (match-string 1 it)) 989 (symbol (intern symbol-name)) 990 (key (where-is-internal symbol keymap t)) 991 (key-description 992 (if key 993 (key-description key) 994 (format "M-x %s" symbol-name)))) 995 (helpful--button 996 key-description 997 'helpful-describe-exactly-button 998 'symbol symbol 999 'callable-p t 1000 'face button-face))) 1001 str 1002 t 1003 t)) 1004 1005 (defun helpful--chars-before (pos n) 1006 "Return up to N chars before POS in the current buffer. 1007 The string may be shorter than N or empty if out-of-range." 1008 (buffer-substring 1009 (max (point-min) (- pos n)) 1010 pos)) 1011 1012 (defun helpful--chars-after (pos n) 1013 "Return up to N chars after POS in the current buffer. 1014 The string may be shorter than N or empty if out-of-range." 1015 (buffer-substring 1016 pos 1017 (min (point-max) (+ pos n)))) 1018 1019 (defun helpful--format-command-keys (docstring) 1020 "Convert command key references and keymap references 1021 in DOCSTRING to buttons. 1022 1023 Emacs uses \\= to escape \\[ references, so replace that 1024 unescaping too." 1025 ;; Loosely based on `substitute-command-keys', but converts 1026 ;; references to buttons. 1027 (let ((keymap nil)) 1028 (with-temp-buffer 1029 (insert docstring) 1030 (goto-char (point-min)) 1031 (while (not (eobp)) 1032 (cond 1033 ((looking-at 1034 ;; Text of the form "foo" 1035 (rx "\"")) 1036 ;; For literal strings, escape backslashes so our output 1037 ;; shows copy-pasteable literals. 1038 (let* ((start-pos (point)) 1039 (end-pos (progn (forward-char) (search-forward "\"" nil t))) 1040 contents) 1041 (if end-pos 1042 (progn 1043 (setq contents (buffer-substring start-pos end-pos)) 1044 (delete-region start-pos end-pos) 1045 (insert (s-replace "\\" "\\\\" contents))) 1046 (forward-char 1)))) 1047 ((looking-at 1048 ;; Text of the form \=X 1049 (rx "\\=")) 1050 ;; Remove the escaping, then step over the escaped char. 1051 ;; Step over the escaped character. 1052 (delete-region (point) (+ (point) 2)) 1053 (forward-char 1)) 1054 ((looking-at 1055 ;; Text of the form `foo' 1056 (rx "`")) 1057 (let* ((start-pos (point)) 1058 (end-pos (search-forward "'" nil t)) 1059 (contents 1060 (when end-pos 1061 (buffer-substring (1+ start-pos) (1- end-pos))))) 1062 (cond 1063 ((null contents) 1064 ;; If there's no closing ' to match the opening `, just 1065 ;; leave it. 1066 (goto-char (1+ start-pos))) 1067 ((s-contains-p "`" contents) 1068 ;; If we have repeated backticks `foo `bar', leave the 1069 ;; first one. 1070 (goto-char (1+ start-pos))) 1071 ((s-contains-p "\\[" contents) 1072 (delete-region start-pos end-pos) 1073 (insert (helpful--format-commands contents keymap))) 1074 ;; Highlight a normal `foo', extracting the surrounding 1075 ;; text so we can detect e.g. "function `foo'". 1076 (t 1077 (let ((before (helpful--chars-before start-pos 10)) 1078 (after (helpful--chars-after end-pos 10))) 1079 (delete-region start-pos end-pos) 1080 (insert (helpful--propertize-sym-ref contents before after))))))) 1081 ((looking-at 1082 ;; Text of the form \\<foo-keymap> 1083 (rx "\\<" (group (+ (not (in ">")))) ">" 1084 (? "\n"))) 1085 (let* ((symbol-with-parens (match-string 0)) 1086 (symbol-name (match-string 1))) 1087 ;; Remove the original string. 1088 (delete-region (point) 1089 (+ (point) (length symbol-with-parens))) 1090 ;; Set the new keymap. 1091 (setq keymap (symbol-value (intern symbol-name))))) 1092 ((looking-at 1093 ;; Text of the form \\{foo-mode-map} 1094 (rx "\\{" (group (+ (not (in "}")))) "}")) 1095 (let* ((symbol-with-parens (match-string 0)) 1096 (symbol-name (match-string 1)) 1097 (keymap 1098 ;; Gracefully handle variables not being defined. 1099 (ignore-errors 1100 (symbol-value (intern symbol-name))))) 1101 ;; Remove the original string. 1102 (delete-region (point) 1103 (+ (point) (length symbol-with-parens))) 1104 (if keymap 1105 (insert (helpful--format-keymap keymap)) 1106 (insert (format "Keymap %s is not currently defined." 1107 symbol-name))))) 1108 ((looking-at 1109 ;; Text of the form \\[foo-command] 1110 (rx "\\[" (group (+ (not (in "]")))) "]")) 1111 (let* ((symbol-with-parens (match-string 0))) 1112 ;; Remove the original string. 1113 (delete-region (point) 1114 (+ (point) (length symbol-with-parens))) 1115 ;; Add a button. 1116 (insert (helpful--format-commands symbol-with-parens keymap)))) 1117 ;; Don't modify other characters. 1118 (t 1119 (forward-char 1)))) 1120 (buffer-string)))) 1121 1122 ;; TODO: fix upstream Emacs bug that means `-map' is not highlighted 1123 ;; in the docstring for `--map'. 1124 (defun helpful--format-docstring (docstring) 1125 "Replace cross-references with links in DOCSTRING." 1126 (-> docstring 1127 (helpful--split-first-line) 1128 (helpful--propertize-info) 1129 (helpful--propertize-links) 1130 (helpful--propertize-bare-links) 1131 (helpful--format-command-keys) 1132 (s-trim))) 1133 1134 (define-button-type 'helpful-link-button 1135 'action #'helpful--follow-link 1136 'follow-link t 1137 'help-echo "Follow this link") 1138 1139 (defun helpful--propertize-links (docstring) 1140 "Convert URL links in docstrings to buttons." 1141 (replace-regexp-in-string 1142 (rx "URL `" (group (*? any)) "'") 1143 (lambda (match) 1144 (let ((url (match-string 1 match))) 1145 (concat "URL " 1146 (helpful--button 1147 url 1148 'helpful-link-button 1149 'url url)))) 1150 docstring)) 1151 1152 (defun helpful--propertize-bare-links (docstring) 1153 "Convert URL links in docstrings to buttons." 1154 (replace-regexp-in-string 1155 (rx (group (or string-start space "<")) 1156 (group "http" (? "s") "://" (+? (not (any space)))) 1157 (group (? (any "." ">" ")")) 1158 (or space string-end ">"))) 1159 (lambda (match) 1160 (let ((space-before (match-string 1 match)) 1161 (url (match-string 2 match)) 1162 (after (match-string 3 match))) 1163 (concat 1164 space-before 1165 (helpful--button 1166 url 1167 'helpful-link-button 1168 'url url) 1169 after))) 1170 docstring)) 1171 1172 (defun helpful--follow-link (button) 1173 "Follow the URL specified by BUTTON." 1174 (browse-url (button-get button 'url))) 1175 1176 (defconst helpful--highlighting-funcs 1177 '(ert--activate-font-lock-keywords 1178 highlight-quoted-mode 1179 rainbow-delimiters-mode) 1180 "Highlighting functions that are safe to run in a temporary buffer. 1181 This is used in `helpful--syntax-highlight' to support extra 1182 highlighting that the user may have configured in their mode 1183 hooks.") 1184 1185 ;; TODO: crashes on `backtrace-frame' on a recent checkout. 1186 1187 (defun helpful--syntax-highlight (source &optional mode) 1188 "Return a propertized version of SOURCE in MODE." 1189 (unless mode 1190 (setq mode #'emacs-lisp-mode)) 1191 (if (or 1192 (< (length source) helpful-max-highlight) 1193 (eq mode 'emacs-lisp-mode)) 1194 (with-temp-buffer 1195 (insert source) 1196 1197 ;; Switch to major-mode MODE, but don't run any hooks. 1198 (delay-mode-hooks (funcall mode)) 1199 1200 ;; `delayed-mode-hooks' contains mode hooks like 1201 ;; `emacs-lisp-mode-hook'. Build a list of functions that are run 1202 ;; when the mode hooks run. 1203 (let (hook-funcs) 1204 (dolist (hook delayed-mode-hooks) 1205 (let ((funcs (symbol-value hook))) 1206 (setq hook-funcs (append hook-funcs funcs)))) 1207 1208 ;; Filter hooks to those that relate to highlighting, and run them. 1209 (setq hook-funcs (-intersection hook-funcs helpful--highlighting-funcs)) 1210 (-map #'funcall hook-funcs)) 1211 1212 (if (fboundp 'font-lock-ensure) 1213 (font-lock-ensure) 1214 (with-no-warnings 1215 (font-lock-fontify-buffer))) 1216 (buffer-string)) 1217 ;; SOURCE was too long to highlight in a reasonable amount of 1218 ;; time. 1219 (concat 1220 (propertize 1221 "// Skipping highlighting due to " 1222 'face 'font-lock-comment-face) 1223 (helpful--button 1224 "helpful-max-highlight" 1225 'helpful-describe-exactly-button 1226 'symbol 'helpful-max-highlight 1227 'callable-p nil) 1228 (propertize 1229 ".\n" 1230 'face 'font-lock-comment-face) 1231 source))) 1232 1233 (defun helpful--source (sym callable-p buf pos) 1234 "Return the source code of SYM. 1235 If the source code cannot be found, return the sexp used." 1236 (catch 'source 1237 (unless (symbolp sym) 1238 (throw 'source sym)) 1239 1240 (let ((source nil)) 1241 (when (and buf pos) 1242 (with-current-buffer buf 1243 (save-excursion 1244 (save-restriction 1245 (goto-char pos) 1246 1247 (if (and (helpful--primitive-p sym callable-p) 1248 (not callable-p)) 1249 ;; For variables defined in .c files, only show the 1250 ;; DEFVAR expression rather than the huge containing 1251 ;; function. 1252 (progn 1253 (setq pos (line-beginning-position)) 1254 (forward-list) 1255 (forward-char) 1256 (narrow-to-region pos (point))) 1257 ;; Narrow to the top-level definition. 1258 (let ((parse-sexp-ignore-comments t)) 1259 (narrow-to-defun t))) 1260 1261 ;; If there was a preceding comment, POS will be 1262 ;; after that comment. Move the position to include that comment. 1263 (setq pos (point-min)) 1264 1265 (setq source (buffer-substring-no-properties (point-min) (point-max)))))) 1266 (setq source (s-trim-right source)) 1267 (when (and source (buffer-file-name buf)) 1268 (setq source (propertize source 1269 'helpful-path (buffer-file-name buf) 1270 'helpful-pos pos 1271 'helpful-pos-is-start t))) 1272 (throw 'source source))) 1273 1274 (when callable-p 1275 ;; Could not find source -- probably defined interactively, or via 1276 ;; a macro, or file has changed. 1277 ;; TODO: verify that the source hasn't changed before showing. 1278 ;; TODO: offer to download C sources for current version. 1279 (throw 'source (indirect-function sym))))) 1280 1281 (defun helpful--has-shortdoc-p (sym) 1282 "Return non-nil if shortdoc.el is available and SYM is in a shortdoc group." 1283 (and (featurep 'shortdoc) 1284 (shortdoc-function-groups sym))) 1285 1286 (defun helpful--in-manual-p (sym) 1287 "Return non-nil if SYM is in an Info manual." 1288 (let ((completions 1289 (cl-letf (((symbol-function #'message) 1290 (lambda (_format-string &rest _args)))) 1291 (info-lookup->completions 'symbol 'emacs-lisp-mode)))) 1292 (-when-let (buf (get-buffer " temp-info-look")) 1293 (kill-buffer buf)) 1294 (or (assoc sym completions) 1295 (assoc-string sym completions)))) 1296 1297 (defun helpful--version-info (sym) 1298 "If SYM has version information, format and return it. 1299 Return nil otherwise." 1300 (when (symbolp sym) 1301 (let ((package-version 1302 (get sym 'custom-package-version)) 1303 (emacs-version 1304 (get sym 'custom-version))) 1305 (cond 1306 (package-version 1307 (format 1308 "This variable was added, or its default value changed, in %s version %s." 1309 (car package-version) 1310 (cdr package-version))) 1311 (emacs-version 1312 (format 1313 "This variable was added, or its default value changed, in Emacs %s." 1314 emacs-version)))))) 1315 1316 (defun helpful--library-path (library-name) 1317 "Find the absolute path for the source of LIBRARY-NAME. 1318 1319 LIBRARY-NAME takes the form \"foo.el\" , \"foo.el\" or 1320 \"src/foo.c\". 1321 1322 If .elc files exist without the corresponding .el, return nil." 1323 (when (member (f-ext library-name) '("c" "rs")) 1324 (setq library-name 1325 (f-expand library-name 1326 (f-parent find-function-C-source-directory)))) 1327 (condition-case nil 1328 (find-library-name library-name) 1329 (error nil))) 1330 1331 (defun helpful--macroexpand-try (form) 1332 "Try to fully macroexpand FORM. 1333 If it fails, attempt to partially macroexpand FORM." 1334 (catch 'result 1335 (ignore-errors 1336 ;; Happy path: we can fully expand the form. 1337 (throw 'result (macroexpand-all form))) 1338 (ignore-errors 1339 ;; Attempt one level of macroexpansion. 1340 (throw 'result (macroexpand-1 form))) 1341 ;; Fallback: just return the original form. 1342 form)) 1343 1344 (defun helpful--tree-any-p (pred tree) 1345 "Walk TREE, applying PRED to every subtree. 1346 Return t if PRED ever returns t." 1347 (catch 'found 1348 (let ((stack (list tree))) 1349 (while stack 1350 (let ((next (pop stack))) 1351 (cond 1352 ((funcall pred next) 1353 (throw 'found t)) 1354 ((consp next) 1355 (push (car next) stack) 1356 (push (cdr next) stack)))))) 1357 nil)) 1358 1359 (defun helpful--find-by-macroexpanding (buf sym callable-p) 1360 "Search BUF for the definition of SYM by macroexpanding 1361 interesting forms in BUF." 1362 (catch 'found 1363 (with-current-buffer buf 1364 (save-excursion 1365 (goto-char (point-min)) 1366 (condition-case nil 1367 (while t 1368 (let ((form (read (current-buffer))) 1369 (var-def-p 1370 (lambda (sexp) 1371 (and (eq (car-safe sexp) 'defvar) 1372 (eq (car-safe (cdr sexp)) sym)))) 1373 (fn-def-p 1374 (lambda (sexp) 1375 ;; `defun' ultimately expands to `defalias'. 1376 (and (eq (car-safe sexp) 'defalias) 1377 (equal (car-safe (cdr sexp)) `(quote ,sym)))))) 1378 (setq form (helpful--macroexpand-try form)) 1379 1380 (when (helpful--tree-any-p 1381 (if callable-p fn-def-p var-def-p) 1382 form) 1383 ;; `read' puts point at the end of the form, so go 1384 ;; back to the start. 1385 (throw 'found (scan-sexps (point) -1))))) 1386 (end-of-file nil)))))) 1387 1388 (defun helpful--open-if-needed (path) 1389 "Return a list (BUF OPENED) where BUF is a buffer visiting PATH. 1390 If a buffer already exists, return that. If not, open PATH with 1391 the `emacs-lisp-mode' syntax table active but skip any hooks." 1392 (let ((initial-buffers (buffer-list)) 1393 (buf nil) 1394 (opened nil) 1395 ;; Skip running hooks that may prompt the user. 1396 (find-file-hook nil) 1397 ;; If we end up opening a buffer, don't bother with file 1398 ;; variables. It prompts the user, and we discard the buffer 1399 ;; afterwards anyway. 1400 (enable-local-variables nil)) 1401 ;; Opening large .c files can be slow (e.g. when looking at 1402 ;; `defalias'), especially if the user has configured mode hooks. 1403 ;; 1404 ;; Bind `auto-mode-alist' to nil, so we open the buffer in 1405 ;; `fundamental-mode' if it isn't already open. 1406 (let ((auto-mode-alist nil)) 1407 (setq buf (find-file-noselect path))) 1408 1409 (unless (-contains-p initial-buffers buf) 1410 (setq opened t) 1411 1412 (let ((syntax-table emacs-lisp-mode-syntax-table)) 1413 (when (s-ends-with-p ".c" path) 1414 (setq syntax-table (make-syntax-table)) 1415 (c-populate-syntax-table syntax-table)) 1416 1417 ;; If it's a freshly opened buffer, we need to set the syntax 1418 ;; table so we can search correctly. 1419 (with-current-buffer buf 1420 (set-syntax-table syntax-table)))) 1421 1422 (list buf opened))) 1423 1424 (defun helpful--definition (sym callable-p) 1425 "Return a list (BUF POS OPENED) where SYM is defined. 1426 1427 BUF is the buffer containing the definition. If the user wasn't 1428 already visiting this buffer, OPENED is t and callers should kill 1429 the buffer when done. 1430 1431 POS is the position of the start of the definition within the 1432 buffer." 1433 (let ((primitive-p (helpful--primitive-p sym callable-p)) 1434 (library-name nil) 1435 (src-path nil) 1436 (buf nil) 1437 (pos nil) 1438 (opened nil)) 1439 ;; We shouldn't be called on primitive functions if we don't have 1440 ;; a directory of Emacs C sourcecode. 1441 (cl-assert 1442 (or find-function-C-source-directory 1443 (not primitive-p))) 1444 1445 (when (symbolp sym) 1446 (if callable-p 1447 (setq library-name (cdr (find-function-library sym))) 1448 ;; Based on `find-variable-noselect'. 1449 (setq library-name 1450 (or 1451 (symbol-file sym 'defvar) 1452 (help-C-file-name sym 'var))))) 1453 1454 (when library-name 1455 (setq src-path (helpful--library-path library-name))) 1456 1457 (cond 1458 ((and (not (symbolp sym)) (functionp sym)) 1459 (list nil nil nil)) 1460 ((and callable-p library-name) 1461 (when src-path 1462 (-let [(src-buf src-opened) (helpful--open-if-needed src-path)] 1463 (setq buf src-buf) 1464 (setq opened src-opened)) 1465 1466 ;; Based on `find-function-noselect'. 1467 (with-current-buffer buf 1468 ;; `find-function-search-for-symbol' moves point. Prevent 1469 ;; that. 1470 (save-excursion 1471 ;; Narrowing has been fixed upstream: 1472 ;; http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=abd18254aec76b26e86ae27e91d2c916ec20cc46 1473 (save-restriction 1474 (widen) 1475 (setq pos 1476 (cdr (find-function-search-for-symbol sym nil library-name)))))) 1477 ;; If we found the containing buffer, but not the symbol, attempt 1478 ;; to find it by macroexpanding interesting forms. 1479 (when (and buf (not pos)) 1480 (setq pos (helpful--find-by-macroexpanding buf sym t))))) 1481 ;; A function, but no file found. 1482 (callable-p 1483 ;; Functions defined interactively may have an edebug property 1484 ;; that contains the location of the definition. 1485 (-when-let (edebug-info (get sym 'edebug)) 1486 (-let [marker (if (consp edebug-info) 1487 (car edebug-info) 1488 edebug-info)] 1489 (setq buf (marker-buffer marker)) 1490 (setq pos (marker-position marker))))) 1491 ((and (not callable-p) src-path) 1492 (-let [(src-buf src-opened) (helpful--open-if-needed src-path)] 1493 (setq buf src-buf) 1494 (setq opened src-opened) 1495 1496 (with-current-buffer buf 1497 ;; `find-function-search-for-symbol' moves point. Prevent 1498 ;; that. 1499 (save-excursion 1500 (condition-case _err 1501 (setq pos (cdr (find-variable-noselect sym 'defvar))) 1502 (search-failed nil) 1503 ;; If your current Emacs instance doesn't match the source 1504 ;; code configured in find-function-C-source-directory, we can 1505 ;; get an error about not finding source. Try 1506 ;; `default-tab-width' against Emacs trunk. 1507 (error nil))))))) 1508 1509 (list buf pos opened))) 1510 1511 (defun helpful--reference-positions (sym callable-p buf) 1512 "Return all the buffer positions of references to SYM in BUF." 1513 (-let* ((forms-and-bufs 1514 (elisp-refs--search-1 1515 (list buf) 1516 (lambda (buf) 1517 (elisp-refs--read-and-find 1518 buf sym 1519 (if callable-p 1520 #'elisp-refs--function-p 1521 #'elisp-refs--variable-p))))) 1522 ;; Since we only searched one buffer, we know that 1523 ;; forms-and-bufs has only one item. 1524 (forms-and-buf (-first-item forms-and-bufs)) 1525 ((forms . _buf) forms-and-buf)) 1526 (-map 1527 (-lambda ((_code start-pos _end-pos)) start-pos) 1528 forms))) 1529 1530 (defun helpful--all-keymap-syms () 1531 "Return all keymaps defined in this Emacs instance." 1532 (let (keymaps) 1533 (mapatoms 1534 (lambda (sym) 1535 (when (and (boundp sym) (keymapp (symbol-value sym))) 1536 (push sym keymaps)))) 1537 keymaps)) 1538 1539 (defun helpful--key-sequences (command-sym keymap global-keycodes) 1540 "Return all the key sequences of COMMAND-SYM in KEYMAP." 1541 (let* ((keycodes 1542 ;; Look up this command in the keymap, its parent and the 1543 ;; global map. We need to include the global map to find 1544 ;; remapped commands. 1545 (where-is-internal command-sym keymap nil t)) 1546 ;; Look up this command in the parent keymap. 1547 (parent-keymap (keymap-parent keymap)) 1548 (parent-keycodes 1549 (when parent-keymap 1550 (where-is-internal 1551 command-sym (list parent-keymap) nil t))) 1552 ;; Look up this command in the global map. 1553 (global-keycodes 1554 (unless (eq keymap global-map) 1555 global-keycodes))) 1556 (->> keycodes 1557 ;; Ignore keybindings from the parent or global map. 1558 (--remove (or (-contains-p global-keycodes it) 1559 (-contains-p parent-keycodes it))) 1560 ;; Convert raw keycode vectors into human-readable strings. 1561 (-map #'key-description)))) 1562 1563 (defun helpful--keymaps-containing (command-sym) 1564 "Return a list of pairs listing keymap names that contain COMMAND-SYM, 1565 along with the keybindings in each keymap. 1566 1567 Keymap names are typically variable names, but may also be 1568 descriptions of values in `minor-mode-map-alist'. 1569 1570 We ignore keybindings that are menu items, and ignore keybindings 1571 from parent keymaps. 1572 1573 `widget-global-map' is also ignored as it generally contains the 1574 same bindings as `global-map'." 1575 (let* ((keymap-syms (helpful--all-keymap-syms)) 1576 (keymap-sym-vals (-map #'symbol-value keymap-syms)) 1577 (global-keycodes (where-is-internal 1578 command-sym (list global-map) nil t)) 1579 matching-keymaps) 1580 ;; Look for this command in all keymaps bound to variables. 1581 (-map 1582 (-lambda ((keymap-sym . keymap)) 1583 (let ((key-sequences (helpful--key-sequences command-sym keymap global-keycodes))) 1584 (when (and key-sequences (not (eq keymap-sym 'widget-global-map))) 1585 (push (cons (symbol-name keymap-sym) key-sequences) 1586 matching-keymaps)))) 1587 (-zip-pair keymap-syms keymap-sym-vals)) 1588 1589 ;; Look for this command in keymaps used by minor modes that 1590 ;; aren't bound to variables. 1591 (-map 1592 (-lambda ((minor-mode . keymap)) 1593 ;; Only consider this keymap if we didn't find it bound to a variable. 1594 (when (and (keymapp keymap) 1595 (not (memq keymap keymap-sym-vals))) 1596 (let ((key-sequences (helpful--key-sequences command-sym keymap global-keycodes))) 1597 (when key-sequences 1598 (push (cons (format "minor-mode-map-alist (%s)" minor-mode) 1599 key-sequences) 1600 matching-keymaps))))) 1601 ;; TODO: examine `minor-mode-overriding-map-alist' too. 1602 minor-mode-map-alist) 1603 1604 matching-keymaps)) 1605 1606 (defun helpful--merge-alists (l1 l2) 1607 "Given two alists mapping symbols to lists, return a single 1608 alist with the lists concatenated." 1609 (let* ((l1-keys (-map #'-first-item l1)) 1610 (l2-keys (-map #'-first-item l2)) 1611 (l2-extra-keys (-difference l2-keys l1-keys)) 1612 (l2-extra-values 1613 (--map (assoc it l2) l2-extra-keys)) 1614 (l1-with-values 1615 (-map (-lambda ((key . values)) 1616 (cons key (append values 1617 (cdr (assoc key l2))))) 1618 l1))) 1619 (append l1-with-values l2-extra-values))) 1620 1621 (defun helpful--keymaps-containing-aliases (command-sym aliases) 1622 "Return a list of pairs mapping keymap symbols to the 1623 keybindings for COMMAND-SYM in each keymap. 1624 1625 Includes keybindings for aliases, unlike 1626 `helpful--keymaps-containing'." 1627 (let* ((syms (cons command-sym aliases)) 1628 (syms-keymaps (-map #'helpful--keymaps-containing syms))) 1629 (-reduce #'helpful--merge-alists syms-keymaps))) 1630 1631 (defun helpful--format-keys (command-sym aliases) 1632 "Describe all the keys that call COMMAND-SYM." 1633 (let (mode-lines 1634 global-lines) 1635 (--each (helpful--keymaps-containing-aliases command-sym aliases) 1636 (-let [(map . keys) it] 1637 (dolist (key keys) 1638 (push 1639 (format "%s %s" 1640 (propertize map 'face 'font-lock-variable-name-face) 1641 (if (>= emacs-major-version 28) 1642 (propertize key 'face 'help-key-binding) 1643 key)) 1644 (if (eq map 'global-map) global-lines mode-lines))))) 1645 (setq global-lines (-sort #'string< global-lines)) 1646 (setq mode-lines (-sort #'string< mode-lines)) 1647 (-let [lines (-concat global-lines mode-lines)] 1648 (if lines 1649 (s-join "\n" lines) 1650 "This command is not in any keymaps.")))) 1651 1652 (defun helpful--outer-sexp (buf pos) 1653 "Find position POS in BUF, and return the name of the outer sexp, 1654 along with its position. 1655 1656 Moves point in BUF." 1657 (with-current-buffer buf 1658 (goto-char pos) 1659 (let* ((ppss (syntax-ppss)) 1660 (outer-sexp-posns (nth 9 ppss))) 1661 (when outer-sexp-posns 1662 (goto-char (car outer-sexp-posns)))) 1663 (list (point) (-take 2 (read buf))))) 1664 1665 (defun helpful--count-values (items) 1666 "Return an alist of the count of each value in ITEMS. 1667 E.g. (x x y z y) -> ((x . 2) (y . 2) (z . 1))" 1668 (let (counts) 1669 (dolist (item items (nreverse counts)) 1670 (-if-let (item-and-count (assoc item counts)) 1671 (setcdr item-and-count (1+ (cdr item-and-count))) 1672 (push (cons item 1) counts))))) 1673 1674 (defun helpful--without-advice (sym) 1675 "Given advised function SYM, return the function object 1676 without the advice. Assumes function has been loaded." 1677 (advice--cd*r 1678 (advice--symbol-function sym))) 1679 1680 (defun helpful--advised-p (sym) 1681 "Does SYM have advice associated with it?" 1682 (and (symbolp sym) 1683 (advice--p (advice--symbol-function sym)))) 1684 1685 (defun helpful--format-head (head) 1686 "Given a 'head' (the first two symbols of a sexp) format and 1687 syntax highlight it." 1688 (-let* (((def name) head) 1689 (formatted-name 1690 (if (and (consp name) (eq (car name) 'quote)) 1691 (format "'%S" (cadr name)) 1692 (format "%S" name))) 1693 (formatted-def 1694 (format "(%s %s ...)" def formatted-name)) 1695 ) 1696 (helpful--syntax-highlight formatted-def))) 1697 1698 (defun helpful--format-reference (head longest-head ref-count position path) 1699 "Return a syntax-highlighted version of HEAD, with a link 1700 to its source location." 1701 (let ((formatted-count 1702 (format "%d reference%s" 1703 ref-count (if (> ref-count 1) "s" "")))) 1704 (propertize 1705 (format 1706 "%s %s" 1707 (s-pad-right longest-head " " (helpful--format-head head)) 1708 (propertize formatted-count 'face 'font-lock-comment-face)) 1709 'helpful-path path 1710 'helpful-pos position))) 1711 1712 (defun helpful--format-position-heads (position-heads path) 1713 "Given a list of outer sexps, format them for display. 1714 POSITION-HEADS takes the form ((123 (defun foo)) (456 (defun bar)))." 1715 (let ((longest-head 1716 (->> position-heads 1717 (-map (-lambda ((_pos head)) (helpful--format-head head))) 1718 (-map #'length) 1719 (-max)))) 1720 (->> (helpful--count-values position-heads) 1721 (-map (-lambda (((pos head) . count)) 1722 (helpful--format-reference head longest-head count pos path))) 1723 (s-join "\n")))) 1724 1725 (defun helpful--primitive-p (sym callable-p) 1726 "Return t if SYM is defined in C." 1727 (let ((subrp (if (fboundp 'subr-primitive-p) 1728 #'subr-primitive-p 1729 #'subrp))) 1730 (cond 1731 ((and callable-p (helpful--advised-p sym)) 1732 (funcall subrp (helpful--without-advice sym))) 1733 (callable-p 1734 (funcall subrp (indirect-function sym))) 1735 (t 1736 (let ((filename (find-lisp-object-file-name sym 'defvar))) 1737 (or (eq filename 'C-source) 1738 (and (stringp filename) 1739 (let ((ext (file-name-extension filename))) 1740 (or (equal ext "c") 1741 (equal ext "rs")))))))))) 1742 1743 (defun helpful--sym-value (sym buf) 1744 "Return the value of SYM in BUF." 1745 (cond 1746 ;; If we're given a buffer, look up the variable in that buffer. 1747 (buf 1748 (with-current-buffer buf 1749 (symbol-value sym))) 1750 ;; If we don't have a buffer, and this is a buffer-local variable, 1751 ;; ensure we return the default value. 1752 ((local-variable-if-set-p sym) 1753 (default-value sym)) 1754 ;; Otherwise, just return the value in the current buffer, which is 1755 ;; the global value. 1756 (t 1757 (symbol-value sym)))) 1758 1759 (defun helpful--insert-section-break () 1760 "Insert section break into helpful buffer." 1761 (insert "\n\n")) 1762 1763 (defun helpful--insert-implementations () 1764 "When `helpful--sym' is a generic method, insert its implementations." 1765 (let ((func helpful--sym) 1766 (content)) 1767 (when (fboundp #'cl--generic-describe) 1768 (with-temp-buffer 1769 (declare-function cl--generic-describe "cl-generic" (function)) 1770 (cl--generic-describe func) 1771 (goto-char (point-min)) 1772 (when (re-search-forward "^Implementations:$" nil t) 1773 (setq content (buffer-substring (point) (point-max))))) 1774 (when content 1775 (helpful--insert-section-break) 1776 (insert (helpful--heading "Implementations") (s-trim content)))))) 1777 1778 (defun helpful--calculate-references (sym callable-p source-path) 1779 "Calculate references for SYM in SOURCE-PATH." 1780 (when source-path 1781 (let* ((primitive-p (helpful--primitive-p sym callable-p)) 1782 (buf (elisp-refs--contents-buffer source-path)) 1783 (positions 1784 (if primitive-p 1785 nil 1786 (helpful--reference-positions 1787 helpful--sym helpful--callable-p buf))) 1788 (return-value (--map (helpful--outer-sexp buf it) positions))) 1789 (kill-buffer buf) 1790 return-value))) 1791 1792 (defun helpful--make-shortdoc-sentence (sym) 1793 "Make a line for shortdoc groups of SYM." 1794 (when (featurep 'shortdoc) 1795 (-when-let (groups (--map (helpful--button 1796 (symbol-name it) 1797 'helpful-shortdoc-button 1798 'shortdoc-group it) 1799 (shortdoc-function-groups sym))) 1800 (if (= 1 (length groups)) 1801 (format "Other relevant functions are documented in the %s group." 1802 (car groups)) 1803 (format "Other relevant functions are documented in the %s groups." 1804 (concat (s-join ", " (butlast groups)) 1805 " and " (car (last groups)))))))) 1806 1807 (defun helpful--make-manual-button (sym) 1808 "Make manual button for SYM." 1809 (helpful--button 1810 "View in manual" 1811 'helpful-manual-button 1812 'symbol sym)) 1813 1814 (defun helpful--make-toggle-button (sym buffer) 1815 "Make toggle button for SYM in BUFFER." 1816 (helpful--button 1817 "Toggle" 1818 'helpful-toggle-button 1819 'symbol sym 1820 'buffer buffer)) 1821 1822 (defun helpful--make-set-button (sym buffer) 1823 "Make set button for SYM in BUFFER." 1824 (helpful--button 1825 "Set" 1826 'helpful-set-button 1827 'symbol sym 1828 'buffer buffer)) 1829 1830 (defun helpful--make-toggle-literal-button () 1831 "Make set button for SYM in BUFFER." 1832 (helpful--button 1833 (if helpful--view-literal 1834 ;; TODO: only offer for strings that have newlines, tabs or 1835 ;; properties. 1836 "Pretty view" 1837 "View as literal") 1838 'helpful-view-literal-button)) 1839 1840 (defun helpful--make-customize-button (sym) 1841 "Make customize button for SYM." 1842 (helpful--button 1843 "Customize" 1844 'helpful-customize-button 1845 'symbol sym)) 1846 1847 (defun helpful--make-references-button (sym callable-p) 1848 "Make references button for SYM." 1849 (helpful--button 1850 "Find all references" 1851 'helpful-all-references-button 1852 'symbol sym 1853 'callable-p callable-p)) 1854 1855 (defun helpful--make-edebug-button (sym) 1856 "Make edebug button for SYM." 1857 (helpful--button 1858 (format "%s edebug" 1859 (if (helpful--edebug-p sym) 1860 "Disable" "Enable")) 1861 'helpful-edebug-button 1862 'symbol sym)) 1863 1864 (defun helpful--make-tracing-button (sym) 1865 "Make tracing button for SYM." 1866 (helpful--button 1867 (format "%s tracing" 1868 (if (trace-is-traced sym) 1869 "Disable" "Enable")) 1870 'helpful-trace-button 1871 'symbol sym)) 1872 1873 (defun helpful--make-disassemble-button (obj) 1874 "Make disassemble button for OBJ. 1875 OBJ may be a symbol or a compiled function object." 1876 (helpful--button 1877 "Disassemble" 1878 'helpful-disassemble-button 1879 'object obj)) 1880 1881 (defun helpful--make-run-test-button (sym) 1882 "Make an ERT test button for SYM." 1883 (helpful--button 1884 "Run test" 1885 'helpful-run-test-button 1886 'symbol sym)) 1887 1888 (defun helpful--make-forget-button (sym callable-p) 1889 "Make forget button for SYM." 1890 (helpful--button 1891 "Forget" 1892 'helpful-forget-button 1893 'symbol sym 1894 'callable-p callable-p)) 1895 1896 (defun helpful--make-callees-button (sym source) 1897 (helpful--button 1898 (format "Functions used by %s" sym) 1899 'helpful-callees-button 1900 'symbol sym 1901 'source source)) 1902 1903 ;; TODO: this only reports if a function is autoloaded because we 1904 ;; autoloaded it. This ignores newly defined functions that are 1905 ;; autoloaded. Built-in help has this limitation too, but if we can 1906 ;; find the source, we should instead see if there's an autoload 1907 ;; cookie. 1908 (defun helpful--autoloaded-p (sym buf) 1909 "Return non-nil if function SYM is autoloaded." 1910 (-when-let (file-name (buffer-file-name buf)) 1911 (setq file-name (s-chop-suffix ".gz" file-name)) 1912 (condition-case nil 1913 (help-fns--autoloaded-p sym file-name) 1914 ; new in Emacs 29.0.50 1915 ; see https://github.com/Wilfred/helpful/pull/283 1916 (error (help-fns--autoloaded-p sym))))) 1917 1918 (defun helpful--compiled-p (sym) 1919 "Return non-nil if function SYM is byte-compiled" 1920 (and (symbolp sym) 1921 (byte-code-function-p (symbol-function sym)))) 1922 1923 (defun helpful--native-compiled-p (sym) 1924 "Return non-nil if function SYM is native-compiled" 1925 (and (symbolp sym) 1926 (fboundp 'subr-native-elisp-p) 1927 (subr-native-elisp-p (symbol-function sym)))) 1928 1929 (defun helpful--join-and (items) 1930 "Join a list of strings with commas and \"and\"." 1931 (cond 1932 ((= (length items) 0) 1933 "") 1934 ((= (length items) 1) 1935 (car items)) 1936 (t 1937 (format "%s and %s" 1938 (s-join ", " (-drop-last 1 items)) 1939 (-last-item items))))) 1940 1941 (defun helpful--summary (sym callable-p buf pos) 1942 "Return a one sentence summary for SYM." 1943 (-let* ((primitive-p (helpful--primitive-p sym callable-p)) 1944 (canonical-sym (helpful--canonical-symbol sym callable-p)) 1945 (alias-p (not (eq canonical-sym sym))) 1946 (alias-button 1947 (if callable-p 1948 ;; Show a link to 'defalias' in the manual. 1949 (helpful--button 1950 "function alias" 1951 'helpful-manual-button 1952 'symbol 'defalias) 1953 ;; Show a link to the variable aliases section in the 1954 ;; manual. 1955 (helpful--button 1956 "alias" 1957 'helpful-info-button 1958 'info-node "(elisp)Variable Aliases"))) 1959 (special-form-button 1960 (helpful--button 1961 "special form" 1962 'helpful-info-button 1963 'info-node "(elisp)Special Forms")) 1964 (keyboard-macro-button 1965 (helpful--button 1966 "keyboard macro" 1967 'helpful-info-button 1968 'info-node "(elisp)Keyboard Macros")) 1969 (interactive-button 1970 (helpful--button 1971 "interactive" 1972 'helpful-info-button 1973 'info-node "(elisp)Using Interactive")) 1974 (autoload-button 1975 (helpful--button 1976 "autoloaded" 1977 'helpful-info-button 1978 'info-node "(elisp)Autoload")) 1979 (compiled-button 1980 (helpful--button 1981 "byte-compiled" 1982 'helpful-info-button 1983 'info-node "(elisp)Byte Compilation")) 1984 (native-compiled-button 1985 (helpful--button 1986 "natively compiled" 1987 'helpful-describe-button 1988 'symbol 'native-compile)) 1989 (buffer-local-button 1990 (helpful--button 1991 "buffer-local" 1992 'helpful-info-button 1993 'info-node "(elisp)Buffer-Local Variables")) 1994 (autoloaded-p 1995 (and callable-p buf (helpful--autoloaded-p sym buf))) 1996 (compiled-p 1997 (and callable-p (helpful--compiled-p sym))) 1998 (native-compiled-p 1999 (and callable-p (helpful--native-compiled-p sym))) 2000 (buttons 2001 (list 2002 (if alias-p alias-button) 2003 (if (and callable-p autoloaded-p) autoload-button) 2004 (if (and callable-p (commandp sym)) interactive-button) 2005 (if compiled-p compiled-button) 2006 (if native-compiled-p native-compiled-button) 2007 (if (and (not callable-p) (local-variable-if-set-p sym)) 2008 buffer-local-button))) 2009 (description 2010 (helpful--join-and (-non-nil buttons))) 2011 (kind 2012 (cond 2013 ((special-form-p sym) 2014 special-form-button) 2015 (alias-p 2016 (format "for %s," 2017 (helpful--button 2018 (symbol-name canonical-sym) 2019 'helpful-describe-exactly-button 2020 'symbol canonical-sym 2021 'callable-p callable-p))) 2022 ((not callable-p) "variable") 2023 ((macrop sym) "macro") 2024 ((helpful--kbd-macro-p sym) keyboard-macro-button) 2025 (t "function"))) 2026 (defined 2027 (cond 2028 (buf 2029 (let ((path (buffer-file-name buf))) 2030 (if path 2031 (format 2032 "defined in %s" 2033 (helpful--navigate-button 2034 (file-name-nondirectory path) path pos)) 2035 (format "defined in buffer %s" 2036 (helpful--buffer-button buf pos))))) 2037 (primitive-p 2038 "defined in C source code") 2039 ((helpful--kbd-macro-p sym) nil) 2040 (t 2041 "without a source file")))) 2042 2043 (s-word-wrap 2044 70 2045 (format "%s is %s %s %s%s." 2046 (if (symbolp sym) 2047 (helpful--format-symbol sym) 2048 "This lambda") 2049 (if (string-match-p 2050 (rx bos (or "a" "e" "i" "o" "u")) 2051 description) 2052 "an" 2053 "a") 2054 description 2055 kind 2056 (if defined (concat " " defined) ""))))) 2057 2058 (defun helpful--callees (form) 2059 "Given source code FORM, return a list of all the functions called." 2060 (let* ((expanded-form (macroexpand-all form)) 2061 ;; Find all the functions called after macro expansion. 2062 (all-fns (helpful--callees-1 expanded-form)) 2063 ;; Only consider the functions that were in the original code 2064 ;; before macro expansion. 2065 (form-syms (-filter #'symbolp (-flatten form))) 2066 (form-fns (--filter (memq it form-syms) all-fns))) 2067 (-distinct form-fns))) 2068 2069 (defun helpful--callees-1 (form) 2070 "Return a list of all the functions called in FORM. 2071 Assumes FORM has been macro expanded. The returned list 2072 may contain duplicates." 2073 (cond 2074 ((not (consp form)) 2075 nil) 2076 ;; See `(elisp)Special Forms'. For these special forms, we recurse 2077 ;; just like functions but ignore the car. 2078 ((memq (car form) '(and catch defconst defvar if interactive 2079 or prog1 prog2 progn save-current-buffer 2080 save-restriction setq setq-default 2081 track-mouse unwind-protect while)) 2082 (-flatten 2083 (-map #'helpful--callees-1 (cdr form)))) 2084 2085 ((eq (car form) 'cond) 2086 (let* ((clauses (cdr form)) 2087 (clause-fns 2088 ;; Each clause is a list of forms. 2089 (--map 2090 (-map #'helpful--callees-1 it) clauses))) 2091 (-flatten clause-fns))) 2092 2093 ((eq (car form) 'condition-case) 2094 (let* ((protected-form (nth 2 form)) 2095 (protected-form-fns (helpful--callees-1 protected-form)) 2096 (handlers (-drop 3 form)) 2097 (handler-bodies (-map #'cdr handlers)) 2098 (handler-fns 2099 (--map 2100 (-map #'helpful--callees-1 it) handler-bodies))) 2101 (append 2102 protected-form-fns 2103 (-flatten handler-fns)))) 2104 2105 ;; Calling a function with a well known higher order function, for 2106 ;; example (funcall 'foo 1 2). 2107 ((and 2108 (memq (car form) '(funcall apply call-interactively 2109 mapcar mapc mapconcat -map)) 2110 (eq (car-safe (nth 1 form)) 'quote)) 2111 (cons 2112 (cadr (nth 1 form)) 2113 (-flatten 2114 (-map #'helpful--callees-1 (cdr form))))) 2115 2116 ((eq (car form) 'function) 2117 (let ((arg (nth 1 form))) 2118 (if (symbolp arg) 2119 ;; #'foo, which is the same as (function foo), is a function 2120 ;; reference. 2121 (list arg) 2122 ;; Handle (function (lambda ...)). 2123 (helpful--callees-1 arg)))) 2124 2125 ((eq (car form) 'lambda) 2126 ;; Only consider the body, not the param list. 2127 (-flatten (-map #'helpful--callees-1 (-drop 2 form)))) 2128 2129 ((eq (car form) 'closure) 2130 ;; Same as lambda, but has an additional argument of the 2131 ;; closed-over variables. 2132 (-flatten (-map #'helpful--callees-1 (-drop 3 form)))) 2133 2134 ((memq (car form) '(let let*)) 2135 ;; Extract function calls used to set the let-bound variables. 2136 (let* ((var-vals (-second-item form)) 2137 (var-val-callees 2138 (--map 2139 (if (consp it) 2140 (-map #'helpful--callees-1 it) 2141 nil) 2142 var-vals))) 2143 (append 2144 (-flatten var-val-callees) 2145 ;; Function calls in the let body. 2146 (-map #'helpful--callees-1 (-drop 2 form))))) 2147 2148 ((eq (car form) 'quote) 2149 nil) 2150 (t 2151 (cons 2152 (car form) 2153 (-flatten 2154 (-map #'helpful--callees-1 (cdr form))))))) 2155 2156 (defun helpful--ensure-loaded () 2157 "Ensure the symbol associated with the current buffer has been loaded." 2158 (when (and helpful--callable-p 2159 (symbolp helpful--sym)) 2160 (let ((fn-obj (symbol-function helpful--sym))) 2161 (when (autoloadp fn-obj) 2162 (autoload-do-load fn-obj))))) 2163 2164 (defun helpful--hook-p (symbol value) 2165 "Does SYMBOL look like a hook?" 2166 (and 2167 (or 2168 (s-ends-with-p "-hook" (symbol-name symbol)) 2169 ;; E.g. `after-change-functions', which can be used with 2170 ;; `add-hook'. 2171 (s-ends-with-p "-functions" (symbol-name symbol))) 2172 (consp value))) 2173 2174 (defun helpful--format-value (sym value) 2175 "Format VALUE as a string." 2176 (cond 2177 (helpful--view-literal 2178 (helpful--syntax-highlight (helpful--pretty-print value))) 2179 ;; Allow strings to be viewed with properties rendered in 2180 ;; Emacs, rather than as a literal. 2181 ((stringp value) 2182 value) 2183 ;; Allow keymaps to be viewed with keybindings shown and 2184 ;; links to the commands bound. 2185 ((keymapp value) 2186 (helpful--format-keymap value)) 2187 ((helpful--hook-p sym value) 2188 (helpful--format-hook value)) 2189 (t 2190 (helpful--pretty-print value)))) 2191 2192 (defun helpful--original-value (sym) 2193 "Return the original value for SYM, if any. 2194 2195 If SYM has an original value, return it in a list. Return nil 2196 otherwise." 2197 (let* ((orig-val-expr (get sym 'standard-value))) 2198 (when (consp orig-val-expr) 2199 (ignore-errors 2200 (list 2201 (eval (car orig-val-expr))))))) 2202 2203 (defun helpful--original-value-differs-p (sym) 2204 "Return t if SYM has an original value, and its current 2205 value is different." 2206 (let ((orig-val-list (helpful--original-value sym))) 2207 (and (consp orig-val-list) 2208 (not (eq (car orig-val-list) 2209 (symbol-value sym)))))) 2210 2211 (defun helpful-update () 2212 "Update the current *Helpful* buffer to the latest 2213 state of the current symbol." 2214 (interactive) 2215 (cl-assert (not (null helpful--sym))) 2216 (unless (buffer-live-p helpful--associated-buffer) 2217 (setq helpful--associated-buffer nil)) 2218 (helpful--ensure-loaded) 2219 (-let* ((val 2220 ;; Look at the value before setting `inhibit-read-only', so 2221 ;; users can see the correct value of that variable. 2222 (unless helpful--callable-p 2223 (helpful--sym-value helpful--sym helpful--associated-buffer))) 2224 (inhibit-read-only t) 2225 (start-line (line-number-at-pos)) 2226 (start-column (current-column)) 2227 (primitive-p (helpful--primitive-p helpful--sym helpful--callable-p)) 2228 (canonical-sym (helpful--canonical-symbol helpful--sym helpful--callable-p)) 2229 (look-for-src (or (not primitive-p) 2230 find-function-C-source-directory)) 2231 ((buf pos opened) 2232 (if look-for-src 2233 (helpful--definition helpful--sym helpful--callable-p) 2234 '(nil nil nil))) 2235 (source (when look-for-src 2236 (helpful--source helpful--sym helpful--callable-p buf pos))) 2237 (source-path (when buf 2238 (buffer-file-name buf))) 2239 (references (helpful--calculate-references 2240 helpful--sym helpful--callable-p 2241 source-path)) 2242 (aliases (helpful--aliases helpful--sym helpful--callable-p))) 2243 2244 (erase-buffer) 2245 2246 (insert (helpful--summary helpful--sym helpful--callable-p buf pos)) 2247 2248 (when (helpful--obsolete-info helpful--sym helpful--callable-p) 2249 (insert 2250 "\n\n" 2251 (helpful--format-obsolete-info helpful--sym helpful--callable-p))) 2252 2253 (when (and helpful--callable-p 2254 (not (helpful--kbd-macro-p helpful--sym))) 2255 (helpful--insert-section-break) 2256 (insert 2257 (helpful--heading "Signature") 2258 (helpful--syntax-highlight (helpful--signature helpful--sym)))) 2259 2260 (when (not helpful--callable-p) 2261 (helpful--insert-section-break) 2262 (let* ((sym helpful--sym) 2263 (multiple-views-p 2264 (or (stringp val) 2265 (keymapp val) 2266 (helpful--hook-p sym val)))) 2267 (when helpful--first-display 2268 (if (stringp val) 2269 ;; For strings, it's more intuitive to display them as 2270 ;; literals, so "1" and 1 are distinct. 2271 (setq helpful--view-literal t) 2272 ;; For everything else, prefer the pretty view if available. 2273 (setq helpful--view-literal nil))) 2274 (insert 2275 (helpful--heading 2276 (cond 2277 ;; Buffer-local variable and we're looking at the value in 2278 ;; a specific buffer. 2279 ((and 2280 helpful--associated-buffer 2281 (local-variable-p sym helpful--associated-buffer)) 2282 (format "Value in %s" 2283 (helpful--button 2284 (format "#<buffer %s>" (buffer-name helpful--associated-buffer)) 2285 'helpful-buffer-button 2286 'buffer helpful--associated-buffer 2287 'position pos))) 2288 ;; Buffer-local variable but default/global value. 2289 ((local-variable-if-set-p sym) 2290 "Global Value") 2291 ;; This variable is not buffer-local. 2292 (t "Value"))) 2293 (helpful--format-value sym val) 2294 "\n\n") 2295 (when (helpful--original-value-differs-p sym) 2296 (insert 2297 (helpful--heading "Original Value") 2298 (helpful--format-value 2299 sym 2300 (car (helpful--original-value sym))) 2301 "\n\n")) 2302 (when multiple-views-p 2303 (insert (helpful--make-toggle-literal-button) " ")) 2304 2305 (when (local-variable-if-set-p sym) 2306 (insert 2307 (helpful--button 2308 "Buffer values" 2309 'helpful-associated-buffer-button 2310 'symbol sym 2311 'prompt-p t) 2312 " " 2313 (helpful--button 2314 "Global value" 2315 'helpful-associated-buffer-button 2316 'symbol sym 2317 'prompt-p nil) 2318 " ")) 2319 (when (memq (helpful--sym-value helpful--sym helpful--associated-buffer) '(nil t)) 2320 (insert (helpful--make-toggle-button helpful--sym helpful--associated-buffer) " ")) 2321 (insert (helpful--make-set-button helpful--sym helpful--associated-buffer)) 2322 (when (custom-variable-p helpful--sym) 2323 (insert " " (helpful--make-customize-button helpful--sym))))) 2324 2325 (let ((docstring (helpful--docstring helpful--sym helpful--callable-p)) 2326 (version-info (unless helpful--callable-p 2327 (helpful--version-info helpful--sym)))) 2328 (when (or docstring version-info) 2329 (helpful--insert-section-break) 2330 (insert 2331 (helpful--heading "Documentation")) 2332 (when docstring 2333 (insert (helpful--format-docstring docstring))) 2334 (when version-info 2335 (insert "\n\n" (s-word-wrap 70 version-info))) 2336 (when (and (symbolp helpful--sym) 2337 helpful--callable-p 2338 (helpful--has-shortdoc-p helpful--sym)) 2339 (insert "\n\n") 2340 (insert (helpful--make-shortdoc-sentence helpful--sym))) 2341 (when (and (symbolp helpful--sym) (helpful--in-manual-p helpful--sym)) 2342 (insert "\n\n") 2343 (insert (helpful--make-manual-button helpful--sym))))) 2344 2345 ;; Show keybindings. 2346 ;; TODO: allow users to conveniently add and remove keybindings. 2347 (when (commandp helpful--sym) 2348 (helpful--insert-section-break) 2349 (insert 2350 (helpful--heading "Key Bindings") 2351 (helpful--format-keys helpful--sym aliases))) 2352 2353 (helpful--insert-section-break) 2354 2355 (insert 2356 (helpful--heading "References") 2357 (let ((src-button 2358 (when source-path 2359 (helpful--navigate-button 2360 (file-name-nondirectory source-path) 2361 source-path 2362 (or pos 2363 0))))) 2364 (cond 2365 ((and source-path references) 2366 (format "References in %s:\n%s" 2367 src-button 2368 (helpful--format-position-heads references source-path))) 2369 ((and source-path primitive-p) 2370 (format 2371 "Finding references in a .%s file is not supported." 2372 (f-ext source-path))) 2373 (source-path 2374 (format "%s is unused in %s." 2375 helpful--sym 2376 src-button)) 2377 ((and primitive-p (null find-function-C-source-directory)) 2378 "C code is not yet loaded.") 2379 (t 2380 "Could not find source file."))) 2381 "\n\n" 2382 (helpful--make-references-button helpful--sym helpful--callable-p)) 2383 2384 (when (and 2385 helpful--callable-p 2386 (symbolp helpful--sym) 2387 source 2388 (not primitive-p)) 2389 (insert 2390 " " 2391 (helpful--make-callees-button helpful--sym source))) 2392 2393 (when (helpful--advised-p helpful--sym) 2394 (helpful--insert-section-break) 2395 (insert 2396 (helpful--heading "Advice") 2397 (format "This %s is advised." 2398 (if (macrop helpful--sym) "macro" "function")))) 2399 2400 (let ((can-edebug 2401 (helpful--can-edebug-p helpful--sym helpful--callable-p buf pos)) 2402 (can-trace 2403 (and (symbolp helpful--sym) 2404 helpful--callable-p 2405 ;; Tracing uses advice, and you can't apply advice to 2406 ;; primitive functions that are replaced with special 2407 ;; opcodes. For example, `narrow-to-region'. 2408 (not (plist-get (symbol-plist helpful--sym) 'byte-opcode)))) 2409 (can-disassemble 2410 (and helpful--callable-p (not primitive-p))) 2411 (can-forget 2412 (and (not (special-form-p helpful--sym)) 2413 (not primitive-p)))) 2414 (when (or can-edebug can-trace can-disassemble can-forget) 2415 (helpful--insert-section-break) 2416 (insert (helpful--heading "Debugging"))) 2417 (when can-edebug 2418 (insert 2419 (helpful--make-edebug-button helpful--sym))) 2420 (when can-trace 2421 (when can-edebug 2422 (insert " ")) 2423 (insert 2424 (helpful--make-tracing-button helpful--sym))) 2425 2426 (when (and 2427 (or can-edebug can-trace) 2428 (or can-disassemble can-forget)) 2429 (insert "\n")) 2430 2431 (when can-disassemble 2432 (insert (helpful--make-disassemble-button helpful--sym))) 2433 2434 (when can-forget 2435 (when can-disassemble 2436 (insert " ")) 2437 (insert (helpful--make-forget-button helpful--sym helpful--callable-p)))) 2438 2439 (when aliases 2440 (helpful--insert-section-break) 2441 (insert 2442 (helpful--heading "Aliases") 2443 (s-join "\n" (--map (helpful--format-alias it helpful--callable-p) 2444 aliases)))) 2445 2446 (when helpful--callable-p 2447 (helpful--insert-implementations)) 2448 2449 (helpful--insert-section-break) 2450 2451 (when (or source-path primitive-p) 2452 (insert 2453 (helpful--heading 2454 (if (eq helpful--sym canonical-sym) 2455 "Source Code" 2456 "Alias Source Code")) 2457 (cond 2458 (source-path 2459 (concat 2460 (propertize (format "%s Defined in " (if primitive-p "//" ";;")) 2461 'face 'font-lock-comment-face) 2462 (helpful--navigate-button 2463 (f-abbrev source-path) 2464 source-path 2465 pos) 2466 "\n")) 2467 (primitive-p 2468 (concat 2469 (propertize 2470 "C code is not yet loaded." 2471 'face 'font-lock-comment-face) 2472 "\n\n" 2473 (helpful--button 2474 "Set C source directory" 2475 'helpful-c-source-directory)))))) 2476 (when source 2477 (insert 2478 (cond 2479 ((stringp source) 2480 (let ((mode (when primitive-p 2481 (pcase (file-name-extension source-path) 2482 ("c" 'c-mode) 2483 ("rs" (when (fboundp 'rust-mode) 'rust-mode)))))) 2484 (helpful--syntax-highlight source mode))) 2485 ((and (consp source) (eq (car source) 'closure)) 2486 (helpful--syntax-highlight 2487 (concat ";; Closure converted to defun by helpful.\n" 2488 (helpful--pretty-print 2489 (helpful--format-closure helpful--sym source))))) 2490 (t 2491 (helpful--syntax-highlight 2492 (concat 2493 (if (eq helpful--sym canonical-sym) 2494 ";; Could not find source code, showing raw function object.\n" 2495 ";; Could not find alias source code, showing raw function object.\n") 2496 (helpful--pretty-print source))))))) 2497 2498 (helpful--insert-section-break) 2499 2500 (-when-let (formatted-props (helpful--format-properties helpful--sym)) 2501 (insert 2502 (helpful--heading "Symbol Properties") 2503 formatted-props)) 2504 2505 (goto-char (point-min)) 2506 (forward-line (1- start-line)) 2507 (forward-char start-column) 2508 (setq helpful--first-display nil) 2509 2510 (when opened 2511 (kill-buffer buf)))) 2512 2513 ;; TODO: this isn't sufficient for `edebug-eval-defun'. 2514 (defun helpful--skip-advice (docstring) 2515 "Remove mentions of advice from DOCSTRING." 2516 (let* ((lines (s-lines docstring)) 2517 (relevant-lines 2518 (--drop-while 2519 (or (s-starts-with-p ":around advice:" it) 2520 (s-starts-with-p "This function has :around advice:" it)) 2521 lines))) 2522 (s-trim (s-join "\n" relevant-lines)))) 2523 2524 (defun helpful--format-argument (arg) 2525 "Format ARG (a symbol) according to Emacs help conventions." 2526 (let ((arg-str (symbol-name arg))) 2527 (if (s-starts-with-p "&" arg-str) 2528 arg-str 2529 (s-upcase arg-str)))) 2530 2531 (defun helpful--format-symbol (sym) 2532 "Format symbol as a string, escaping as necessary." 2533 ;; Arguably this is an Emacs bug. We should be able to use 2534 ;; (format "%S" sym) 2535 ;; but that converts foo? to "foo\\?". You can see this in other 2536 ;; parts of the Emacs UI, such as ERT. 2537 (s-replace " " "\\ " (format "%s" sym))) 2538 2539 ;; TODO: this is broken for -any?. 2540 (defun helpful--signature (sym) 2541 "Get the signature for function SYM, as a string. 2542 For example, \"(some-func FOO &optional BAR)\"." 2543 (let (docstring-sig 2544 source-sig 2545 (advertised-args 2546 (when (symbolp sym) 2547 (gethash (symbol-function sym) advertised-signature-table)))) 2548 ;; Get the usage from the function definition. 2549 (let* ((function-args 2550 (cond 2551 ((symbolp sym) 2552 (help-function-arglist sym)) 2553 ((byte-code-function-p sym) 2554 ;; argdesc can be a list of arguments or an integer 2555 ;; encoding the min/max number of arguments. See 2556 ;; Byte-Code Function Objects in the elisp manual. 2557 (let ((argdesc (aref sym 0))) 2558 (if (consp argdesc) 2559 argdesc 2560 ;; TODO: properly handle argdesc values. 2561 nil))) 2562 (t 2563 ;; Interpreted function (lambda ...) 2564 (cadr sym)))) 2565 (formatted-args 2566 (cond 2567 (advertised-args 2568 (-map #'helpful--format-argument advertised-args)) 2569 ((listp function-args) 2570 (-map #'helpful--format-argument function-args)) 2571 (t 2572 (list function-args))))) 2573 (setq source-sig 2574 (cond 2575 ;; If it's a function object, just show the arguments. 2576 ((not (symbolp sym)) 2577 (format "(%s)" 2578 (s-join " " formatted-args))) 2579 ;; If it has multiple arguments, join them with spaces. 2580 (formatted-args 2581 (format "(%s %s)" 2582 (helpful--format-symbol sym) 2583 (s-join " " formatted-args))) 2584 ;; Otherwise, this function takes no arguments when called. 2585 (t 2586 (format "(%s)" (helpful--format-symbol sym)))))) 2587 2588 ;; If the docstring ends with (fn FOO BAR), extract that. 2589 (-when-let (docstring (documentation sym)) 2590 (-when-let (docstring-with-usage (help-split-fundoc docstring sym)) 2591 (setq docstring-sig (car docstring-with-usage)))) 2592 2593 (cond 2594 ;; Advertised signature always wins. 2595 (advertised-args 2596 source-sig) 2597 ;; If that's not set, use the usage specification in the 2598 ;; docstring, if present. 2599 (docstring-sig 2600 (replace-regexp-in-string "\\\\=\\(['\\`‘’]\\)" "\\1" docstring-sig t)) 2601 (t 2602 ;; Otherwise, just use the signature from the source code. 2603 source-sig)))) 2604 2605 (defun helpful--format-obsolete-info (sym callable-p) 2606 (-let [(use _ date) (helpful--obsolete-info sym callable-p)] 2607 (helpful--format-docstring 2608 (s-word-wrap 2609 70 2610 (format "This %s is obsolete%s%s" 2611 (helpful--kind-name sym callable-p) 2612 (if date (format " since %s" date) 2613 "") 2614 (cond ((stringp use) (concat "; " use)) 2615 (use (format "; use `%s' instead." use)) 2616 (t "."))))))) 2617 2618 (defun helpful--docstring (sym callable-p) 2619 "Get the docstring for SYM. 2620 Note that this returns the raw docstring, including \\=\\= 2621 escapes that are used by `substitute-command-keys'." 2622 (let ((text-quoting-style 'grave) 2623 docstring) 2624 (if callable-p 2625 (progn 2626 (setq docstring (documentation sym t)) 2627 (-when-let (docstring-with-usage (help-split-fundoc docstring sym)) 2628 (setq docstring (cdr docstring-with-usage)) 2629 (when docstring 2630 ;; Advice mutates the docstring, see 2631 ;; `advice--make-docstring'. Undo that. 2632 ;; TODO: Only do this if the function is advised. 2633 (setq docstring (helpful--skip-advice docstring))))) 2634 (setq docstring 2635 (documentation-property sym 'variable-documentation t))) 2636 docstring)) 2637 2638 (defun helpful--read-symbol (prompt default-val predicate) 2639 "Read a symbol from the minibuffer, with completion. 2640 Returns the symbol." 2641 (when (and default-val 2642 (not (funcall predicate default-val))) 2643 (setq default-val nil)) 2644 (when default-val 2645 ;; `completing-read' expects a string. 2646 (setq default-val (symbol-name default-val)) 2647 2648 ;; TODO: Only modify the prompt when we don't have ido/ivy/helm, 2649 ;; because the default is obvious for them. 2650 (setq prompt 2651 (replace-regexp-in-string 2652 (rx ": " eos) 2653 (format " (default: %s): " default-val) 2654 prompt))) 2655 (intern (completing-read prompt obarray 2656 predicate t nil nil 2657 default-val))) 2658 2659 (defun helpful--update-and-switch-buffer (symbol callable-p) 2660 "Update and switch to help buffer for SYMBOL." 2661 (let ((buf (helpful--buffer symbol callable-p))) 2662 (with-current-buffer buf 2663 (helpful-update)) 2664 (funcall helpful-switch-buffer-function buf))) 2665 2666 ;;;###autoload 2667 (defun helpful-function (symbol) 2668 "Show help for function named SYMBOL. 2669 2670 See also `helpful-macro', `helpful-command' and `helpful-callable'." 2671 (interactive 2672 (list (helpful--read-symbol 2673 "Function: " 2674 (helpful--callable-at-point) 2675 #'functionp))) 2676 (helpful--update-and-switch-buffer symbol t)) 2677 2678 ;;;###autoload 2679 (defun helpful-command (symbol) 2680 "Show help for interactive function named SYMBOL. 2681 2682 See also `helpful-function'." 2683 (interactive 2684 (list (helpful--read-symbol 2685 "Command: " 2686 (helpful--callable-at-point) 2687 #'commandp))) 2688 (helpful--update-and-switch-buffer symbol t)) 2689 2690 ;;;###autoload 2691 (defun helpful-key (key-sequence) 2692 "Show help for interactive command bound to KEY-SEQUENCE." 2693 (interactive 2694 (list (read-key-sequence "Press key: "))) 2695 (let ((sym (key-binding key-sequence))) 2696 (cond 2697 ((null sym) 2698 (user-error "No command is bound to %s" 2699 (key-description key-sequence))) 2700 ((commandp sym) 2701 (helpful--update-and-switch-buffer sym t)) 2702 (t 2703 (user-error "%s is bound to %s which is not a command" 2704 (key-description key-sequence) 2705 sym))))) 2706 2707 ;;;###autoload 2708 (defun helpful-macro (symbol) 2709 "Show help for macro named SYMBOL." 2710 (interactive 2711 (list (helpful--read-symbol 2712 "Macro: " 2713 (helpful--callable-at-point) 2714 #'macrop))) 2715 (helpful--update-and-switch-buffer symbol t)) 2716 2717 ;;;###autoload 2718 (defun helpful-callable (symbol) 2719 "Show help for function, macro or special form named SYMBOL. 2720 2721 See also `helpful-macro', `helpful-function' and `helpful-command'." 2722 (interactive 2723 (list (helpful--read-symbol 2724 "Callable: " 2725 (helpful--callable-at-point) 2726 #'fboundp))) 2727 (helpful--update-and-switch-buffer symbol t)) 2728 2729 (defun helpful--variable-p (symbol) 2730 "Return non-nil if SYMBOL is a variable." 2731 (or (get symbol 'variable-documentation) 2732 (and (boundp symbol) 2733 (not (keywordp symbol)) 2734 (not (eq symbol nil)) 2735 (not (eq symbol t))))) 2736 2737 (defun helpful--bound-p (symbol) 2738 "Return non-nil if SYMBOL is a variable or callable. 2739 2740 This differs from `boundp' because we do not consider nil, t 2741 or :foo." 2742 (or (fboundp symbol) 2743 (helpful--variable-p symbol))) 2744 2745 (defun helpful--bookmark-jump (bookmark) 2746 "Create and switch to helpful bookmark BOOKMARK." 2747 (let ((callable-p (bookmark-prop-get bookmark 'callable-p)) 2748 (sym (bookmark-prop-get bookmark 'sym)) 2749 (position (bookmark-prop-get bookmark 'position))) 2750 (if callable-p 2751 (helpful-callable sym) 2752 (helpful-variable sym)) 2753 (goto-char position))) 2754 2755 (defun helpful--bookmark-make-record () 2756 "Create a bookmark record for helpful buffers. 2757 2758 See docs of `bookmark-make-record-function'." 2759 `((sym . ,helpful--sym) 2760 (callable-p . ,helpful--callable-p) 2761 (position . ,(point)) 2762 (handler . helpful--bookmark-jump))) 2763 2764 (defun helpful--convert-c-name (symbol var) 2765 "Convert SYMBOL from a C name to an Elisp name. 2766 E.g. convert `Fmake_string' to `make-string' or 2767 `Vgc_cons_percentage' to `gc-cons-percentage'. Interpret 2768 SYMBOL as variable name if VAR, else a function name. Return 2769 nil if SYMBOL doesn't begin with \"F\" or \"V\"." 2770 (let ((string (symbol-name symbol)) 2771 (prefix (if var "V" "F"))) 2772 (when (s-starts-with-p prefix string) 2773 (intern 2774 (s-chop-prefix 2775 prefix 2776 (s-replace "_" "-" string)))))) 2777 2778 ;;;###autoload 2779 (defun helpful-symbol (symbol) 2780 "Show help for SYMBOL, a variable, function or macro. 2781 2782 See also `helpful-callable' and `helpful-variable'." 2783 (interactive 2784 (list (helpful--read-symbol 2785 "Symbol: " 2786 (helpful--symbol-at-point) 2787 #'helpful--bound-p))) 2788 (let ((c-var-sym (helpful--convert-c-name symbol t)) 2789 (c-fn-sym (helpful--convert-c-name symbol nil))) 2790 (cond 2791 ((and (boundp symbol) (fboundp symbol)) 2792 (if (y-or-n-p 2793 (format "%s is a both a variable and a callable, show variable?" 2794 symbol)) 2795 (helpful-variable symbol) 2796 (helpful-callable symbol))) 2797 ((fboundp symbol) 2798 (helpful-callable symbol)) 2799 ((boundp symbol) 2800 (helpful-variable symbol)) 2801 ((and c-fn-sym (fboundp c-fn-sym)) 2802 (helpful-callable c-fn-sym)) 2803 ((and c-var-sym (boundp c-var-sym)) 2804 (helpful-variable c-var-sym)) 2805 (t 2806 (user-error "Not bound: %S" symbol))))) 2807 2808 ;;;###autoload 2809 (defun helpful-variable (symbol) 2810 "Show help for variable named SYMBOL." 2811 (interactive 2812 (list (helpful--read-symbol 2813 "Variable: " 2814 (helpful--variable-at-point) 2815 #'helpful--variable-p))) 2816 (helpful--update-and-switch-buffer symbol nil)) 2817 2818 (defun helpful--variable-at-point-exactly () 2819 "Return the symbol at point, if it's a bound variable." 2820 (let ((var (variable-at-point))) 2821 ;; `variable-at-point' uses 0 rather than nil to signify no symbol 2822 ;; at point (presumably because 'nil is a symbol). 2823 (unless (symbolp var) 2824 (setq var nil)) 2825 (when (helpful--variable-p var) 2826 var))) 2827 2828 (defun helpful--variable-defined-at-point () 2829 "Return the variable defined in the form enclosing point." 2830 ;; TODO: do the same thing if point is just before a top-level form. 2831 (save-excursion 2832 (save-restriction 2833 (widen) 2834 (let* ((ppss (syntax-ppss)) 2835 (sexp-start (nth 1 ppss)) 2836 sexp) 2837 (when sexp-start 2838 (goto-char sexp-start) 2839 (setq sexp (condition-case nil 2840 (read (current-buffer)) 2841 (error nil))) 2842 (when (memq (car-safe sexp) 2843 (list 'defvar 'defvar-local 'defcustom 'defconst)) 2844 (nth 1 sexp))))))) 2845 2846 (defun helpful--variable-at-point () 2847 "Return the variable exactly under point, or defined at point." 2848 (let ((var (helpful--variable-at-point-exactly))) 2849 (if var 2850 var 2851 (let ((var (helpful--variable-defined-at-point))) 2852 (when (helpful--variable-p var) 2853 var))))) 2854 2855 (defun helpful--callable-at-point () 2856 (let ((sym (symbol-at-point)) 2857 (enclosing-sym (function-called-at-point))) 2858 (if (fboundp sym) 2859 sym 2860 enclosing-sym))) 2861 2862 (defun helpful--symbol-at-point-exactly () 2863 "Return the symbol at point, if it's bound." 2864 (let ((sym (symbol-at-point))) 2865 (when (helpful--bound-p sym) 2866 sym))) 2867 2868 (defun helpful--symbol-at-point () 2869 "Find the most relevant symbol at or around point. 2870 Returns nil if nothing found." 2871 (or 2872 (helpful--symbol-at-point-exactly) 2873 (helpful--callable-at-point) 2874 (helpful--variable-at-point))) 2875 2876 ;;;###autoload 2877 (defun helpful-at-point () 2878 "Show help for the symbol at point." 2879 (interactive) 2880 (-if-let (symbol (helpful--symbol-at-point)) 2881 (helpful-symbol symbol) 2882 (user-error "There is no symbol at point."))) 2883 2884 (defun helpful--imenu-index () 2885 "Return a list of headings in the current buffer, suitable for 2886 imenu." 2887 (let (headings) 2888 (goto-char (point-min)) 2889 (while (not (eobp)) 2890 (when (eq (get-text-property (point) 'face) 2891 'helpful-heading) 2892 (push 2893 (cons 2894 (buffer-substring-no-properties 2895 (line-beginning-position) (line-end-position)) 2896 (line-beginning-position)) 2897 headings)) 2898 (forward-line)) 2899 (nreverse headings))) 2900 2901 (defun helpful--flash-region (start end) 2902 "Temporarily highlight region from START to END." 2903 (let ((overlay (make-overlay start end))) 2904 (overlay-put overlay 'face 'highlight) 2905 (run-with-timer 1.5 nil 'delete-overlay overlay))) 2906 2907 (defun helpful-visit-reference () 2908 "Go to the reference at point." 2909 (interactive) 2910 (let* ((sym helpful--sym) 2911 (path (get-text-property (point) 'helpful-path)) 2912 (pos (get-text-property (point) 'helpful-pos)) 2913 (pos-is-start (get-text-property (point) 'helpful-pos-is-start))) 2914 (when (and path pos) 2915 ;; If we're looking at a source excerpt, calculate the offset of 2916 ;; point, so we don't just go the start of the excerpt. 2917 (when pos-is-start 2918 (save-excursion 2919 (let ((offset 0)) 2920 (while (and 2921 (get-text-property (point) 'helpful-pos) 2922 (not (eobp))) 2923 (backward-char 1) 2924 (setq offset (1+ offset))) 2925 ;; On the last iteration we moved outside the source 2926 ;; excerpt, so we overcounted by one character. 2927 (setq offset (1- offset)) 2928 2929 ;; Set POS so we go to exactly the place in the source 2930 ;; code where point was in the helpful excerpt. 2931 (setq pos (+ pos offset))))) 2932 2933 (find-file path) 2934 (helpful--goto-char-widen pos) 2935 (recenter 0) 2936 (save-excursion 2937 (let ((defun-end (scan-sexps (point) 1))) 2938 (while (re-search-forward 2939 (rx-to-string `(seq symbol-start ,(symbol-name sym) symbol-end)) 2940 defun-end t) 2941 (helpful--flash-region (match-beginning 0) (match-end 0)))))))) 2942 2943 (defun helpful-kill-buffers () 2944 "Kill all `helpful-mode' buffers. 2945 2946 See also `helpful-max-buffers'." 2947 (interactive) 2948 (dolist (buffer (buffer-list)) 2949 (when (eq (buffer-local-value 'major-mode buffer) 'helpful-mode) 2950 (kill-buffer buffer)))) 2951 2952 (defvar helpful-mode-map 2953 (let* ((map (make-sparse-keymap))) 2954 (define-key map (kbd "g") #'helpful-update) 2955 (define-key map [remap revert-buffer] #'helpful-update) 2956 (when (fboundp 'revert-buffer-quick) 2957 (define-key map [remap revert-buffer-quick] #'helpful-update)) 2958 2959 (define-key map (kbd "RET") #'helpful-visit-reference) 2960 2961 (define-key map (kbd "TAB") #'forward-button) 2962 (define-key map (kbd "<backtab>") #'backward-button) 2963 2964 (define-key map (kbd "n") #'forward-button) 2965 (define-key map (kbd "p") #'backward-button) 2966 map) 2967 "Keymap for `helpful-mode'.") 2968 2969 (declare-function bookmark-prop-get "bookmark" (bookmark prop)) 2970 (declare-function bookmark-make-record-default "bookmark" 2971 (&optional no-file no-context posn)) 2972 ;; Ensure this variable is defined even if bookmark.el isn't loaded 2973 ;; yet. This follows the pattern in help-mode.el.gz. 2974 ;; TODO: find a cleaner solution. 2975 (defvar bookmark-make-record-function) 2976 2977 (defun helpful--add-support-for-org-links () 2978 "Improve support for org \"help\" links through helpful." 2979 (helpful--support-storing-org-links) 2980 (helpful--prefer-helpful-when-following-org-link)) 2981 2982 (defun helpful--support-storing-org-links () 2983 "Make `org-store-link' in a helpful buffer return a \"help\" link." 2984 (when (and (fboundp 'org-link-set-parameters) 2985 (not (-contains-p (org-link-types) "helpful"))) 2986 (org-link-set-parameters "helpful" 2987 :store #'helpful--org-link-store))) 2988 2989 (defun helpful--org-link-store () 2990 "Store \"help\" type link when in a helpful buffer." 2991 (when (derived-mode-p 'helpful-mode) 2992 ;; Create a "help" link instead of a dedicated "helpful" link: the 2993 ;; author of the Org document uses helful, but this is not 2994 ;; necessarily the case of the reader of the document. 2995 (org-link-store-props :type "help" 2996 :link (format "help:%s" helpful--sym) 2997 :description nil))) 2998 2999 (defun helpful--prefer-helpful-when-following-org-link () 3000 "Prefer helpful when using `org-open-at-point' on a \"help\" link." 3001 (when (fboundp 'org-link-set-parameters) 3002 (let ((follow-function (org-link-get-parameter "help" :follow))) 3003 (when (not (equal follow-function #'helpful--org-link-follow)) 3004 (org-link-set-parameters "help" 3005 :follow #'helpful--org-link-follow))))) 3006 3007 (defun helpful--org-link-follow (link _) 3008 (helpful-symbol (intern link))) 3009 3010 (define-derived-mode helpful-mode special-mode "Helpful" 3011 "Major mode for *Helpful* buffers." 3012 (add-hook 'xref-backend-functions #'elisp--xref-backend nil t) 3013 3014 (setq imenu-create-index-function #'helpful--imenu-index) 3015 ;; Prevent imenu converting "Source Code" to "Source.Code". 3016 (setq-local imenu-space-replacement " ") 3017 3018 ;; Enable users to bookmark helpful buffers. 3019 (set (make-local-variable 'bookmark-make-record-function) 3020 #'helpful--bookmark-make-record) 3021 3022 ;; This function should normally only be called once after Org and 3023 ;; helpful are loaded. To avoid using `eval-after-load' (which is 3024 ;; only recommended in user init files), the function is called each 3025 ;; time the major mode is used. 3026 (helpful--add-support-for-org-links)) 3027 3028 (provide 'helpful) 3029 ;;; helpful.el ends here