diff options
Diffstat (limited to 'lisp/help-fns.el')
-rw-r--r-- | lisp/help-fns.el | 841 |
1 files changed, 497 insertions, 344 deletions
diff --git a/lisp/help-fns.el b/lisp/help-fns.el index f78c6ab0dfa..f200077faec 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -33,6 +33,7 @@ ;;; Code: (require 'cl-lib) +(require 'seq) (require 'help-mode) (require 'radix-tree) (eval-when-compile (require 'subr-x)) ;For when-let. @@ -132,6 +133,14 @@ with the current prefix. The files are chosen according to :group 'help :version "26.3") +(defcustom help-enable-variable-value-editing nil + "If non-nil, allow editing values in *Help* buffers. +Values that aren't readable by the Emacs Lisp reader can't be +edited even if this option is enabled." + :type 'boolean + :group 'help + :version "29.1") + (defcustom help-enable-symbol-autoload nil "Perform autoload if docs are missing from autoload objects." :type 'boolean @@ -249,7 +258,8 @@ handling of autoloaded functions." ;; calling that. (let ((describe-function-orig-buffer (or describe-function-orig-buffer - (current-buffer)))) + (current-buffer))) + (help-buffer-under-preparation t)) (help-setup-xref (list (lambda (function buffer) @@ -394,7 +404,7 @@ if the variable `help-downcase-arguments' is non-nil." ;; `describe-face' (instead of `describe-simplify-lib-file-name'). ;;;###autoload -(defun find-lisp-object-file-name (object type) +(defun find-lisp-object-file-name (object type &optional also-c-source) "Guess the file that defined the Lisp object OBJECT, of type TYPE. OBJECT should be a symbol associated with a function, variable, or face; alternatively, it can be a function definition. @@ -405,8 +415,13 @@ If TYPE is not a symbol, search for a function definition. The return value is the absolute name of a readable file where OBJECT is defined. If several such files exist, preference is given to a file found via `load-path'. The return value can also be `C-source', which -means that OBJECT is a function or variable defined in C. If no -suitable file is found, return nil." +means that OBJECT is a function or variable defined in C, but +it's currently unknown where. If no suitable file is found, +return nil. + +If ALSO-C-SOURCE is non-nil, instead of returning `C-source', +this function will attempt to locate the definition of OBJECT in +the C sources, too." (let* ((autoloaded (autoloadp type)) (file-name (or (and autoloaded (nth 1 type)) (symbol-file @@ -443,14 +458,18 @@ suitable file is found, return nil." (cond ((and (not file-name) (subrp type)) ;; A built-in function. The form is from `describe-function-1'. - (if (get-buffer " *DOC*") + (if (or (get-buffer " *DOC*") + (and also-c-source + (get-buffer-create " *DOC*"))) (help-C-file-name type 'subr) 'C-source)) ((and (not file-name) (symbolp object) (eq type 'defvar) (integerp (get object 'variable-documentation))) ;; A variable defined in C. The form is from `describe-variable'. - (if (get-buffer " *DOC*") + (if (or (get-buffer " *DOC*") + (and also-c-source + (get-buffer-create " *DOC*"))) (help-C-file-name object 'var) 'C-source)) ((not (stringp file-name)) @@ -495,9 +514,16 @@ suitable file is found, return nil." (let ((pt2 (with-current-buffer standard-output (point))) (remapped (command-remapping function))) (unless (memq remapped '(ignore undefined)) - (let ((keys (where-is-internal - (or remapped function) overriding-local-map nil nil)) - non-modified-keys) + (let* ((all-keys (where-is-internal + (or remapped function) overriding-local-map nil nil)) + (seps (seq-group-by + (lambda (key) + (and (vectorp key) + (eq (elt key 0) 'menu-bar))) + all-keys)) + (keys (cdr (assq nil seps))) + (menus (cdr (assq t seps))) + non-modified-keys) (if (and (eq function 'self-insert-command) (vectorp (car-safe keys)) (consp (aref (car keys) 0))) @@ -521,29 +547,85 @@ suitable file is found, return nil." ;; don't mention them one by one. (if (< (length non-modified-keys) 10) (with-current-buffer standard-output - (insert (mapconcat #'help--key-description-fontified - keys ", "))) + (help-fns--insert-bindings keys)) (dolist (key non-modified-keys) (setq keys (delq key keys))) (if keys (with-current-buffer standard-output - (insert (mapconcat #'help--key-description-fontified - keys ", ")) + (help-fns--insert-bindings keys) (insert ", and many ordinary text characters")) - (princ "many ordinary text characters")))) + (princ "many ordinary text characters.")))) (when (or remapped keys non-modified-keys) (princ ".") - (terpri))))) + (terpri))) - (with-current-buffer standard-output - (fill-region-as-paragraph pt2 (point)) - (unless (looking-back "\n\n" (- (point) 2)) - (terpri)))))) + (with-current-buffer standard-output + (fill-region-as-paragraph pt2 (point)) + (unless (bolp) + (insert "\n")) + (when menus + (let ((start (point))) + (help-fns--insert-menu-bindings + menus + (concat "It can " (and keys "also ") + "be invoked from the menu: ")) + (fill-region-as-paragraph start (point)))) + (ensure-empty-lines))))))) + +(defun help-fns--insert-bindings (keys) + (seq-do-indexed (lambda (key i) + (insert + (cond ((zerop i) "") + ((= i (1- (length keys))) " and ") + (t ", "))) + (insert (help--key-description-fontified key))) + keys)) + +(defun help-fns--insert-menu-bindings (menus heading) + (seq-do-indexed + (lambda (menu i) + (insert + (cond ((zerop i) "") + ((= i (1- (length menus))) " and ") + (t ", "))) + (let ((map (lookup-key global-map (seq-take menu 1))) + (start (point))) + (seq-do-indexed + (lambda (entry level) + (when (symbolp map) + (setq map (symbol-function map))) + (when-let ((elem (assq entry (cdr map)))) + (when heading + (insert heading) + (setq heading nil start (point))) + (when (> level 0) + (insert + (if (char-displayable-p ?→) + " → " + " => "))) + (if (eq (nth 1 elem) 'menu-item) + (progn + (insert (nth 2 elem)) + (setq map (cadddr elem))) + (insert (nth 1 elem)) + (setq map (cddr elem))))) + (cdr (seq-into menu 'list))) + (put-text-property start (point) 'face 'help-key-binding))) + menus)) (defun help-fns--compiler-macro (function) - (let ((handler (function-get function 'compiler-macro))) + (pcase-dolist (`(,type . ,handler) + (list (cons "compiler macro" + (function-get function 'compiler-macro)) + (cons "`byte-compile' property" + (function-get function 'byte-compile)) + (cons "byte-code optimizer" + (function-get function 'byte-optimizer)))) (when handler - (insert " This function has a compiler macro") + (if (bolp) + (insert " This function has a ") + (insert " and a ")) + (insert type) (if (symbolp handler) (progn (insert (format-message " `%s'" handler)) @@ -558,8 +640,17 @@ suitable file is found, return nil." (save-excursion (re-search-backward (substitute-command-keys "`\\([^`']+\\)'") nil t) - (help-xref-button 1 'help-function-cmacro function lib))))) - (insert ".\n")))) + (help-xref-button 1 'help-function-cmacro function lib))))))) + (unless (bolp) + (insert ". See " + (buttonize "the manual" + (lambda (_) (info "(elisp)Advice and Byte Code"))) + " for details.\n") + (save-restriction + (let ((fill-prefix " ")) + (narrow-to-region (line-beginning-position -1) (point)) + (fill-region (point-min) (point-max))) + (goto-char (point-max))))) (defun help-fns--signature (function doc real-def real-function buffer) "Insert usage at point and return docstring. With highlighting." @@ -652,19 +743,9 @@ suitable file is found, return nil." (terpri))) ;; We could use `symbol-file' but this is a wee bit more efficient. -(defun help-fns--autoloaded-p (function file) - "Return non-nil if FUNCTION has previously been autoloaded. -FILE is the file where FUNCTION was probably defined." - (let* ((file (file-name-sans-extension (file-truename file))) - (load-hist load-history) - (target (cons t function)) - found) - (while (and load-hist (not found)) - (and (stringp (caar load-hist)) - (equal (file-name-sans-extension (caar load-hist)) file) - (setq found (member target (cdar load-hist)))) - (setq load-hist (cdr load-hist))) - found)) +(defun help-fns--autoloaded-p (function) + "Return non-nil if FUNCTION has previously been autoloaded." + (seq-some #'autoloadp (get function 'function-history))) (defun help-fns--interactive-only (function) "Insert some help blurb if FUNCTION should only be used interactively." @@ -718,21 +799,23 @@ FILE is the file where FUNCTION was probably defined." (erase-buffer) (insert-file-contents f) (goto-char (point-min)) - (search-forward "\n*") - (while (re-search-forward re nil t) - (let ((pos (match-beginning 0))) - (save-excursion - ;; Almost all entries are of the form "* ... in Emacs NN.MM." - ;; but there are also a few in the form "* Emacs NN.MM is a bug - ;; fix release ...". - (if (not (re-search-backward "^\\* .* Emacs \\([0-9.]+[0-9]\\)" - nil t)) - (message "Ref found in non-versioned section in %S" - (file-name-nondirectory f)) - (let ((version (match-string 1))) - (when (or (null first) (version< version first)) - (setq place (list f pos)) - (setq first version))))))))) + ;; Failed git merges can leave empty files that look like NEWS + ;; in etc. Don't error here. + (when (search-forward "\n*" nil t) + (while (re-search-forward re nil t) + (let ((pos (match-beginning 0))) + (save-excursion + ;; Almost all entries are of the form "* ... in Emacs NN.MM." + ;; but there are also a few in the form "* Emacs NN.MM is a bug + ;; fix release ...". + (if (not (re-search-backward "^\\* .* Emacs \\([0-9.]+[0-9]\\)" + nil t)) + (message "Ref found in non-versioned section in %S" + (file-name-nondirectory f)) + (let ((version (match-string 1))) + (when (or (null first) (version< version first)) + (setq place (list f pos)) + (setq first version)))))))))) (when first (make-text-button first nil 'type 'help-news 'help-args place)))) @@ -774,7 +857,8 @@ FILE is the file where FUNCTION was probably defined." (insert-text-button (symbol-name group) 'action (lambda (_) - (shortdoc-display-group group object)) + (shortdoc-display-group group object + help-window-keep-selected)) 'follow-link t 'help-echo (purecopy "mouse-1, RET: show documentation group"))) groups) @@ -828,11 +912,7 @@ Returns a list of the form (REAL-FUNCTION DEF ALIASED REAL-DEF)." (symbol-name function))))))) (real-def (cond ((and aliased (not (subrp def))) - (let ((f real-function)) - (while (and (fboundp f) - (symbolp (symbol-function f))) - (setq f (symbol-function f))) - f)) + (car (function-alias-p real-function t))) ((subrp def) (intern (subr-name def))) (t def)))) @@ -851,13 +931,13 @@ Returns a list of the form (REAL-FUNCTION DEF ALIASED REAL-DEF)." "Print a line describing FUNCTION to `standard-output'." (pcase-let* ((`(,_real-function ,def ,aliased ,real-def) (help-fns--analyze-function function)) - (file-name (find-lisp-object-file-name function (if aliased 'defun - def))) + (file-name (find-lisp-object-file-name + function (if aliased 'defun def))) (beg (if (and (or (byte-code-function-p def) (keymapp def) (memq (car-safe def) '(macro lambda closure))) (stringp file-name) - (help-fns--autoloaded-p function file-name)) + (help-fns--autoloaded-p function)) (concat "an autoloaded " (if (commandp def) "interactive ")) @@ -946,12 +1026,18 @@ Returns a list of the form (REAL-FUNCTION DEF ALIASED REAL-DEF)." ;;;###autoload (defun describe-function-1 (function) - (let ((pt1 (with-current-buffer (help-buffer) (point)))) + (let ((pt1 (with-current-buffer standard-output (point)))) (help-fns-function-description-header function) - (with-current-buffer (help-buffer) - (fill-region-as-paragraph (save-excursion (goto-char pt1) (forward-line 0) (point)) - (point)))) - (terpri)(terpri) + (with-current-buffer standard-output + (let ((inhibit-read-only t)) + (fill-region-as-paragraph + (save-excursion + (goto-char pt1) + (forward-line 0) + (point)) + (point) + nil t) + (ensure-empty-lines)))) (pcase-let* ((`(,real-function ,def ,_aliased ,real-def) (help-fns--analyze-function function)) @@ -995,7 +1081,7 @@ Returns a list of the form (REAL-FUNCTION DEF ALIASED REAL-DEF)." (add-hook 'help-fns-describe-function-functions #'help-fns--obsolete) (add-hook 'help-fns-describe-function-functions #'help-fns--interactive-only) (add-hook 'help-fns-describe-function-functions #'help-fns--parent-mode) -(add-hook 'help-fns-describe-function-functions #'help-fns--compiler-macro) +(add-hook 'help-fns-describe-function-functions #'help-fns--compiler-macro 100) ;; Variables @@ -1078,7 +1164,8 @@ it is displayed along with the global value." (if (symbolp v) (symbol-name v)))) (list (if (equal val "") v (intern val))))) - (let (file-name) + (let (file-name + (help-buffer-under-preparation t)) (unless (buffer-live-p buffer) (setq buffer (current-buffer))) (unless (frame-live-p frame) (setq frame (selected-frame))) (if (not (symbolp variable)) @@ -1138,10 +1225,11 @@ it is displayed along with the global value." (let ((rep (let ((print-quoted t) (print-circle t)) - (cl-prin1-to-string val)))) - (if (and (symbolp val) (not (booleanp val))) + (cl-prin1-to-string val)))) + (if (and (symbolp val) (not (booleanp val))) (format-message "`%s'" rep) - rep)))) + rep))) + (start (point))) (if (< (+ (length print-rep) (point) (- line-beg)) 68) (insert " " print-rep) (terpri) @@ -1156,6 +1244,8 @@ it is displayed along with the global value." (insert-buffer-substring pp-buffer))))) ;; Remove trailing newline. (and (= (char-before) ?\n) (delete-char -1))) + (help-fns--editable-variable start (point) + variable val buffer) (let* ((sv (get variable 'standard-value)) (origval (and (consp sv) (condition-case nil @@ -1175,6 +1265,8 @@ it is displayed along with the global value." (save-restriction (narrow-to-region from (point)) (save-excursion (pp-buffer))) + (help-fns--editable-variable from (point) + variable origval buffer) (if (< (point) (+ from 20)) (delete-region (1- from) from))))))) (terpri) @@ -1207,7 +1299,9 @@ it is displayed along with the global value." ;; See previous comment for this function. ;; (help-xref-on-pp from (point)) (if (< (point) (+ from 20)) - (delete-region (1- from) from))))))) + (delete-region (1- from) from)) + (help-fns--editable-variable + from (point) variable global-val buffer)))))) (terpri)) ;; If the value is large, move it to the end. @@ -1257,6 +1351,66 @@ it is displayed along with the global value." ;; Return the text we displayed. (buffer-string)))))))) +(defun help-fns--editable-variable (start end variable value buffer) + (when (and (readablep value) + (or (not (symbolp value)) + (and (not (and (symbolp value) (boundp value))) + (not (and (symbolp value) (fboundp value))))) + help-enable-variable-value-editing) + (add-text-properties + start end + (list 'help-echo "`e' to edit the value" + 'help-fns--edit-variable (list variable value buffer + (current-buffer)) + 'keymap (define-keymap + "e" #'help-fns-edit-variable))))) + +(defvar help-fns--edit-variable) + +(put 'help-fns-edit-variable 'disabled t) +(defun help-fns-edit-variable () + "Edit the variable under point." + (interactive) + (declare (completion ignore)) + (let ((var (get-text-property (point) 'help-fns--edit-variable))) + (unless var + (error "No variable under point")) + (pop-to-buffer-same-window (format "*edit %s*" (nth 0 var))) + (prin1 (nth 1 var) (current-buffer)) + (pp-buffer) + (goto-char (point-min)) + (insert (format ";; Edit the `%s' variable.\n" (nth 0 var)) + ";; C-c C-c to update the value and exit.\n\n") + (help-fns--edit-value-mode) + (setq-local help-fns--edit-variable var))) + +(defvar-keymap help-fns--edit-value-mode-map + "C-c C-c" #'help-fns-edit-mode-done) + +(define-derived-mode help-fns--edit-value-mode emacs-lisp-mode "Elisp" + :interactive nil) + +(defun help-fns-edit-mode-done (&optional kill) + "Update the value of the variable and kill the buffer. +If KILL (the prefix), don't update the value, but just kill the +current buffer." + (interactive "P" help-fns--edit-value-mode) + (unless help-fns--edit-variable + (error "Invalid buffer")) + (goto-char (point-min)) + (cl-destructuring-bind (variable _ buffer help-buffer) + help-fns--edit-variable + (unless (buffer-live-p buffer) + (error "Original buffer is gone; can't update")) + (unless kill + (let ((value (read (current-buffer)))) + (with-current-buffer buffer + (set variable value)))) + (kill-buffer (current-buffer)) + (when (buffer-live-p help-buffer) + (with-current-buffer help-buffer + (revert-buffer))))) + (defun help-fns--run-describe-functions (functions &rest args) (with-current-buffer standard-output (unless (bolp) @@ -1461,77 +1615,78 @@ If FRAME is omitted or nil, use the selected frame." (interactive (list (read-face-name "Describe face" (or (face-at-point t) 'default) t))) - (help-setup-xref (list #'describe-face face) - (called-interactively-p 'interactive)) - (unless face - (setq face 'default)) - (if (not (listp face)) - (setq face (list face))) - (with-help-window (help-buffer) - (with-current-buffer standard-output - (dolist (f face (buffer-string)) - (if (stringp f) (setq f (intern f))) - ;; We may get called for anonymous faces (i.e., faces - ;; expressed using prop-value plists). Those can't be - ;; usefully customized, so ignore them. - (when (symbolp f) - (insert "Face: " (symbol-name f)) - (if (not (facep f)) - (insert " undefined face.\n") - (let ((customize-label "customize this face") - file-name) - (insert (concat " (" (propertize "sample" 'font-lock-face f) ")")) - (princ (concat " (" customize-label ")\n")) - ;; FIXME not sure how much of this belongs here, and - ;; how much in `face-documentation'. The latter is - ;; not used much, but needs to return nil for - ;; undocumented faces. - (let ((alias (get f 'face-alias)) - (face f) - obsolete) - (when alias - (setq face alias) - (insert - (format-message - "\n %s is an alias for the face `%s'.\n%s" - f alias - (if (setq obsolete (get f 'obsolete-face)) - (format-message - " This face is obsolete%s; use `%s' instead.\n" - (if (stringp obsolete) - (format " since %s" obsolete) - "") - alias) - "")))) - (insert "\nDocumentation:\n" - (substitute-command-keys - (or (face-documentation face) - "Not documented as a face.")) - "\n\n")) - (with-current-buffer standard-output - (save-excursion - (re-search-backward - (concat "\\(" customize-label "\\)") nil t) - (help-xref-button 1 'help-customize-face f))) - (setq file-name (find-lisp-object-file-name f 'defface)) - (if (not file-name) - (setq help-mode--current-data (list :symbol f)) - (setq help-mode--current-data (list :symbol f - :file file-name)) - (princ (substitute-command-keys "Defined in `")) - (princ (help-fns-short-filename file-name)) - (princ (substitute-command-keys "'")) - ;; Make a hyperlink to the library. - (save-excursion - (re-search-backward - (substitute-command-keys "`\\([^`']+\\)'") nil t) - (help-xref-button 1 'help-face-def f file-name)) - (princ ".") - (terpri) - (terpri)))) - (terpri) - (help-fns--run-describe-functions - help-fns-describe-face-functions f frame)))))) + (let ((help-buffer-under-preparation t)) + (help-setup-xref (list #'describe-face face) + (called-interactively-p 'interactive)) + (unless face + (setq face 'default)) + (if (not (listp face)) + (setq face (list face))) + (with-help-window (help-buffer) + (with-current-buffer standard-output + (dolist (f face (buffer-string)) + (if (stringp f) (setq f (intern f))) + ;; We may get called for anonymous faces (i.e., faces + ;; expressed using prop-value plists). Those can't be + ;; usefully customized, so ignore them. + (when (symbolp f) + (insert "Face: " (symbol-name f)) + (if (not (facep f)) + (insert " undefined face.\n") + (let ((customize-label "customize this face") + file-name) + (insert (concat " (" (propertize "sample" 'font-lock-face f) ")")) + (princ (concat " (" customize-label ")\n")) + ;; FIXME not sure how much of this belongs here, and + ;; how much in `face-documentation'. The latter is + ;; not used much, but needs to return nil for + ;; undocumented faces. + (let ((alias (get f 'face-alias)) + (face f) + obsolete) + (when alias + (setq face alias) + (insert + (format-message + "\n %s is an alias for the face `%s'.\n%s" + f alias + (if (setq obsolete (get f 'obsolete-face)) + (format-message + " This face is obsolete%s; use `%s' instead.\n" + (if (stringp obsolete) + (format " since %s" obsolete) + "") + alias) + "")))) + (insert "\nDocumentation:\n" + (substitute-command-keys + (or (face-documentation face) + "Not documented as a face.")) + "\n\n")) + (with-current-buffer standard-output + (save-excursion + (re-search-backward + (concat "\\(" customize-label "\\)") nil t) + (help-xref-button 1 'help-customize-face f))) + (setq file-name (find-lisp-object-file-name f 'defface)) + (if (not file-name) + (setq help-mode--current-data (list :symbol f)) + (setq help-mode--current-data (list :symbol f + :file file-name)) + (princ (substitute-command-keys "Defined in `")) + (princ (help-fns-short-filename file-name)) + (princ (substitute-command-keys "'")) + ;; Make a hyperlink to the library. + (save-excursion + (re-search-backward + (substitute-command-keys "`\\([^`']+\\)'") nil t) + (help-xref-button 1 'help-face-def f file-name)) + (princ ".") + (terpri) + (terpri)))) + (terpri) + (help-fns--run-describe-functions + help-fns-describe-face-functions f frame))))))) (add-hook 'help-fns-describe-face-functions #'help-fns--face-custom-version-info) @@ -1561,7 +1716,7 @@ If FRAME is omitted or nil, use the selected frame." (:fontset . "Fontset") (:extend . "Extend") (:inherit . "Inherit"))) - (max-width (apply #'max (mapcar #'(lambda (x) (length (cdr x))) + (max-width (apply #'max (mapcar (lambda (x) (length (cdr x))) attrs)))) (dolist (a attrs) (let ((attr (face-attribute face (car a) frame))) @@ -1602,43 +1757,44 @@ current buffer and the selected frame, respectively." (if found (symbol-name v-or-f))))) (list (if (equal val "") (or v-or-f "") (intern val))))) - (if (not (symbolp symbol)) - (user-error "You didn't specify a function or variable")) - (unless (buffer-live-p buffer) (setq buffer (current-buffer))) - (unless (frame-live-p frame) (setq frame (selected-frame))) - (with-current-buffer (help-buffer) - ;; Push the previous item on the stack before clobbering the output buffer. - (help-setup-xref nil nil) - (let* ((docs - (nreverse - (delq nil - (mapcar (pcase-lambda (`(,name ,testfn ,descfn)) - (when (funcall testfn symbol) - ;; Don't record the current entry in the stack. - (setq help-xref-stack-item nil) - (cons name - (funcall descfn symbol buffer frame)))) - describe-symbol-backends)))) - (single (null (cdr docs)))) - (while (cdr docs) - (goto-char (point-min)) - (let ((inhibit-read-only t) - (name (caar docs)) ;Name of doc currently at BOB. - (doc (cdr (cadr docs)))) ;Doc to add at BOB. - (when doc - (insert doc) - (delete-region (point) - (progn (skip-chars-backward " \t\n") (point))) - (insert "\n\n" (make-separator-line) "\n") - (when name - (insert (symbol-name symbol) - " is also a " name "." "\n\n")))) - (setq docs (cdr docs))) - (unless single - ;; Don't record the `describe-variable' item in the stack. - (setq help-xref-stack-item nil) - (help-setup-xref (list #'describe-symbol symbol) nil)) - (goto-char (point-min))))) + (let ((help-buffer-under-preparation t)) + (if (not (symbolp symbol)) + (user-error "You didn't specify a function or variable")) + (unless (buffer-live-p buffer) (setq buffer (current-buffer))) + (unless (frame-live-p frame) (setq frame (selected-frame))) + (with-current-buffer (help-buffer) + ;; Push the previous item on the stack before clobbering the output buffer. + (help-setup-xref nil nil) + (let* ((docs + (nreverse + (delq nil + (mapcar (pcase-lambda (`(,name ,testfn ,descfn)) + (when (funcall testfn symbol) + ;; Don't record the current entry in the stack. + (setq help-xref-stack-item nil) + (cons name + (funcall descfn symbol buffer frame)))) + describe-symbol-backends)))) + (single (null (cdr docs)))) + (while (cdr docs) + (goto-char (point-min)) + (let ((inhibit-read-only t) + (name (caar docs)) ;Name of doc currently at BOB. + (doc (cdr (cadr docs)))) ;Doc to add at BOB. + (when doc + (insert doc) + (delete-region (point) + (progn (skip-chars-backward " \t\n") (point))) + (insert "\n\n" (make-separator-line) "\n") + (when name + (insert (symbol-name symbol) + " is also a " name "." "\n\n")))) + (setq docs (cdr docs))) + (unless single + ;; Don't record the `describe-variable' item in the stack. + (setq help-xref-stack-item nil) + (help-setup-xref (list #'describe-symbol symbol) nil)) + (goto-char (point-min)))))) ;;;###autoload (defun describe-syntax (&optional buffer) @@ -1647,15 +1803,16 @@ The descriptions are inserted in a help buffer, which is then displayed. BUFFER defaults to the current buffer." (interactive) (setq buffer (or buffer (current-buffer))) - (help-setup-xref (list #'describe-syntax buffer) - (called-interactively-p 'interactive)) - (with-help-window (help-buffer) - (let ((table (with-current-buffer buffer (syntax-table)))) - (with-current-buffer standard-output - (describe-vector table 'internal-describe-syntax-value) - (while (setq table (char-table-parent table)) - (insert "\nThe parent syntax table is:") - (describe-vector table 'internal-describe-syntax-value)))))) + (let ((help-buffer-under-preparation t)) + (help-setup-xref (list #'describe-syntax buffer) + (called-interactively-p 'interactive)) + (with-help-window (help-buffer) + (let ((table (with-current-buffer buffer (syntax-table)))) + (with-current-buffer standard-output + (describe-vector table 'internal-describe-syntax-value) + (while (setq table (char-table-parent table)) + (insert "\nThe parent syntax table is:") + (describe-vector table 'internal-describe-syntax-value))))))) (defun help-describe-category-set (value) (insert (cond @@ -1672,59 +1829,60 @@ The descriptions are inserted in a buffer, which is then displayed. If BUFFER is non-nil, then describe BUFFER's category table instead. BUFFER should be a buffer or a buffer name." (interactive) - (setq buffer (or buffer (current-buffer))) - (help-setup-xref (list #'describe-categories buffer) - (called-interactively-p 'interactive)) - (with-help-window (help-buffer) - (let* ((table (with-current-buffer buffer (category-table))) - (docs (char-table-extra-slot table 0))) - (if (or (not (vectorp docs)) (/= (length docs) 95)) - (error "Invalid first extra slot in this category table\n")) - (with-current-buffer standard-output - (setq-default help-button-cache (make-marker)) - (insert "Legend of category mnemonics ") - (insert-button "(longer descriptions at the bottom)" - 'action help-button-cache - 'follow-link t - 'help-echo "mouse-2, RET: show full legend") - (insert "\n") - (let ((pos (point)) (items 0) lines n) - (dotimes (i 95) - (if (aref docs i) (setq items (1+ items)))) - (setq lines (1+ (/ (1- items) 4))) - (setq n 0) + (let ((help-buffer-under-preparation t)) + (setq buffer (or buffer (current-buffer))) + (help-setup-xref (list #'describe-categories buffer) + (called-interactively-p 'interactive)) + (with-help-window (help-buffer) + (let* ((table (with-current-buffer buffer (category-table))) + (docs (char-table-extra-slot table 0))) + (if (or (not (vectorp docs)) (/= (length docs) 95)) + (error "Invalid first extra slot in this category table\n")) + (with-current-buffer standard-output + (setq-default help-button-cache (make-marker)) + (insert "Legend of category mnemonics ") + (insert-button "(longer descriptions at the bottom)" + 'action help-button-cache + 'follow-link t + 'help-echo "mouse-2, RET: show full legend") + (insert "\n") + (let ((pos (point)) (items 0) lines n) + (dotimes (i 95) + (if (aref docs i) (setq items (1+ items)))) + (setq lines (1+ (/ (1- items) 4))) + (setq n 0) + (dotimes (i 95) + (let ((elt (aref docs i))) + (when elt + (string-match ".*" elt) + (setq elt (match-string 0 elt)) + (if (>= (length elt) 17) + (setq elt (concat (substring elt 0 14) "..."))) + (if (< (point) (point-max)) + (move-to-column (* 20 (/ n lines)) t)) + (insert (+ i ?\s) ?: elt) + (if (< (point) (point-max)) + (forward-line 1) + (insert "\n")) + (setq n (1+ n)) + (if (= (% n lines) 0) + (goto-char pos)))))) + (goto-char (point-max)) + (insert "\n" + "character(s)\tcategory mnemonics\n" + "------------\t------------------") + (describe-vector table 'help-describe-category-set) + (set-marker help-button-cache (point)) + (insert "Legend of category mnemonics:\n") (dotimes (i 95) (let ((elt (aref docs i))) (when elt - (string-match ".*" elt) - (setq elt (match-string 0 elt)) - (if (>= (length elt) 17) - (setq elt (concat (substring elt 0 14) "..."))) - (if (< (point) (point-max)) - (move-to-column (* 20 (/ n lines)) t)) - (insert (+ i ?\s) ?: elt) - (if (< (point) (point-max)) - (forward-line 1) - (insert "\n")) - (setq n (1+ n)) - (if (= (% n lines) 0) - (goto-char pos)))))) - (goto-char (point-max)) - (insert "\n" - "character(s)\tcategory mnemonics\n" - "------------\t------------------") - (describe-vector table 'help-describe-category-set) - (set-marker help-button-cache (point)) - (insert "Legend of category mnemonics:\n") - (dotimes (i 95) - (let ((elt (aref docs i))) - (when elt - (if (string-match "\n" elt) - (setq elt (substring elt (match-end 0)))) - (insert (+ i ?\s) ": " elt "\n")))) - (while (setq table (char-table-parent table)) - (insert "\nThe parent category table is:") - (describe-vector table 'help-describe-category-set)))))) + (if (string-match "\n" elt) + (setq elt (substring elt (match-end 0)))) + (insert (+ i ?\s) ": " elt "\n")))) + (while (setq table (char-table-parent table)) + (insert "\nThe parent category table is:") + (describe-vector table 'help-describe-category-set))))))) (defun help-fns-find-keymap-name (keymap) "Find the name of the variable with value KEYMAP. @@ -1746,8 +1904,8 @@ variable with value KEYMAP." The heuristic to determine which keymap is most likely to be relevant to a user follows this order: -1. 'keymap' text property at point -2. 'local-map' text property at point +1. `keymap' text property at point +2. `local-map' text property at point 3. the `current-local-map' This is used to set the default value for the interactive prompt @@ -1766,7 +1924,10 @@ in `describe-keymap'. See also `Searching the Active Keymaps'." When called interactively, prompt for a variable that has a keymap value." (interactive - (let* ((km (help-fns--most-relevant-active-keymap)) + (let* ((sym (symbol-at-point)) + (km (or (and (keymapp (ignore-errors (symbol-value sym))) + sym) + (help-fns--most-relevant-active-keymap))) (val (completing-read (format-prompt "Keymap" km) obarray @@ -1778,7 +1939,8 @@ keymap value." (unless (and km (keymapp (symbol-value km))) (user-error "Not a keymap: %s" km)) (list km))) - (let (used-gentemp) + (let (used-gentemp + (help-buffer-under-preparation t)) (unless (and (symbolp keymap) (boundp keymap) (keymapp (symbol-value keymap))) @@ -1844,106 +2006,96 @@ whose documentation describes the minor mode. If called from Lisp with a non-nil BUFFER argument, display documentation for the major and minor modes of that buffer." (interactive "@") - (unless buffer (setq buffer (current-buffer))) - (help-setup-xref (list #'describe-mode buffer) - (called-interactively-p 'interactive)) - ;; For the sake of help-do-xref and help-xref-go-back, - ;; don't switch buffers before calling `help-buffer'. - (with-help-window (help-buffer) - (with-current-buffer buffer - (let (minors) - ;; Older packages do not register in minor-mode-list but only in - ;; minor-mode-alist. - (dolist (x minor-mode-alist) - (setq x (car x)) - (unless (memq x minor-mode-list) - (push x minor-mode-list))) - ;; Find enabled minor mode we will want to mention. - (dolist (mode minor-mode-list) - ;; Document a minor mode if it is listed in minor-mode-alist, - ;; non-nil, and has a function definition. - (let ((fmode (or (get mode :minor-mode-function) mode))) - (and (boundp mode) (symbol-value mode) - (fboundp fmode) - (let ((pretty-minor-mode - (if (string-match "\\(\\(-minor\\)?-mode\\)?\\'" - (symbol-name fmode)) - (capitalize - (substring (symbol-name fmode) - 0 (match-beginning 0))) - fmode))) - (push (list fmode pretty-minor-mode - (format-mode-line (assq mode minor-mode-alist))) - minors))))) - ;; Narrowing is not a minor mode, but its indicator is part of - ;; mode-line-modes. - (when (buffer-narrowed-p) - (push '(narrow-to-region "Narrow" " Narrow") minors)) - (setq minors - (sort minors - (lambda (a b) (string-lessp (cadr a) (cadr b))))) - (when minors - (princ "Enabled minor modes:\n") - (make-local-variable 'help-button-cache) - (with-current-buffer standard-output - (dolist (mode minors) - (let ((mode-function (nth 0 mode)) - (pretty-minor-mode (nth 1 mode)) - (indicator (nth 2 mode))) - (save-excursion - (goto-char (point-max)) - (princ "\n\f\n") - (push (point-marker) help-button-cache) - ;; Document the minor modes fully. - (insert-text-button - pretty-minor-mode 'type 'help-function - 'help-args (list mode-function) - 'button '(t)) - (princ (format " minor mode (%s):\n" - (if (zerop (length indicator)) - "no indicator" - (format "indicator%s" - indicator)))) - (princ (help-split-fundoc (documentation mode-function) - nil 'doc))) - (insert-button pretty-minor-mode - 'action (car help-button-cache) - 'follow-link t - 'help-echo "mouse-2, RET: show full information") - (newline))) - (forward-line -1) - (fill-paragraph nil) - (forward-line 1)) - - (princ "\n(Information about these minor modes follows the major mode info.)\n\n")) - ;; Document the major mode. - (let ((mode mode-name)) - (with-current-buffer standard-output - (let ((start (point))) - (insert (format-mode-line mode nil nil buffer)) - (add-text-properties start (point) '(face bold))))) - (princ " mode") - (let* ((mode major-mode) - (file-name (find-lisp-object-file-name mode nil))) - (if (not file-name) - (setq help-mode--current-data (list :symbol mode)) - (princ (format-message " defined in `%s'" - (help-fns-short-filename file-name))) - ;; Make a hyperlink to the library. - (with-current-buffer standard-output - (save-excursion - (re-search-backward (substitute-command-keys "`\\([^`']+\\)'") - nil t) - (setq help-mode--current-data (list :symbol mode - :file file-name)) - (help-xref-button 1 'help-function-def mode file-name))))) - (let ((fundoc (help-split-fundoc (documentation major-mode) nil 'doc))) - (with-current-buffer standard-output - (insert ":\n") - (insert fundoc) - (insert (help-fns--list-local-commands))))))) - ;; For the sake of IELM and maybe others - nil) + (unless buffer + (setq buffer (current-buffer))) + (let ((help-buffer-under-preparation t) + (local-minors (buffer-local-value 'local-minor-modes buffer))) + (help-setup-xref (list #'describe-mode buffer) + (called-interactively-p 'interactive)) + ;; For the sake of help-do-xref and help-xref-go-back, + ;; don't switch buffers before calling `help-buffer'. + (with-help-window (help-buffer) + (with-current-buffer (help-buffer) + ;; Add the local minor modes at the start. + (when local-minors + (insert (format "Minor mode%s enabled in this buffer:" + (if (length> local-minors 1) + "s" ""))) + (describe-mode--minor-modes local-minors)) + + ;; Document the major mode. + (let ((major (buffer-local-value 'major-mode buffer))) + (insert "The major mode is " + (buttonize + (propertize (format-mode-line + (buffer-local-value 'mode-name buffer) + nil nil buffer) + 'face 'bold) + (lambda (_) + (describe-function major)))) + (insert " mode") + (when-let ((file-name (find-lisp-object-file-name major nil))) + (insert (format " defined in %s:\n\n" + (buttonize + (help-fns-short-filename file-name) + (lambda (_) + (help-function-def--button-function + major file-name)))))) + (insert (help-split-fundoc (documentation major) nil 'doc) + (with-current-buffer buffer + (help-fns--list-local-commands))) + (ensure-empty-lines 1) + + ;; Insert the global minor modes after the major mode. + (when global-minor-modes + (insert (format "Global minor mode%s enabled:" + (if (length> global-minor-modes 1) + "s" ""))) + (describe-mode--minor-modes global-minor-modes) + (when (re-search-forward "^\f") + (beginning-of-line) + (ensure-empty-lines 1))) + ;; For the sake of IELM and maybe others + nil))))) + +(defun describe-mode--minor-modes (modes) + (dolist (mode (seq-sort #'string< modes)) + (let ((pretty-minor-mode + (capitalize + (replace-regexp-in-string + "\\(\\(-minor\\)?-mode\\)?\\'" "" + (symbol-name mode))))) + (insert + " " + (buttonize + pretty-minor-mode + (lambda (mode) + (goto-char (point-min)) + (text-property-search-forward + 'help-minor-mode mode t) + (beginning-of-line)) + mode)) + (save-excursion + (goto-char (point-max)) + (insert "\n\n\f\n") + ;; Document the minor modes fully. + (insert (buttonize + (propertize pretty-minor-mode 'help-minor-mode mode) + (lambda (mode) + (describe-function mode)) + mode)) + (let ((indicator + (format-mode-line (assq mode minor-mode-alist)))) + (insert (format " minor mode (%s):\n" + (if (zerop (length indicator)) + "no indicator" + (format "indicator%s" + indicator))))) + (insert (help-split-fundoc (documentation mode) nil 'doc))))) + (forward-line -1) + (fill-paragraph nil) + (forward-paragraph 1) + (ensure-empty-lines 1)) (defun help-fns--list-local-commands () (let ((functions nil)) @@ -1998,7 +2150,8 @@ one of them returns non-nil." (event-end key)) ((eq key ?\C-g) (signal 'quit nil)) (t (user-error "You didn't specify a widget")))))) - (let (buf) + (let (buf + (help-buffer-under-preparation t)) ;; Allow describing a widget in a different window. (when (posnp pos) (setq buf (window-buffer (posn-window pos)) |