When I browse Emacs help for functions via
C-h f, I often want to peek into the Elisp/C implementation. I want to enter
view-mode automatically when I access source code this way to avoid unnecessary modification. Is there a hook or function I can advise to accomplish this?
When I browse Emacs help for functions via
Update (after a night of sleep): This answer has a major flaw: it enables
view-mode when navigating to any function, not just Emacs sources. This can be fixed, but you're better off using the answer by @phils.
C-h f describe-function RET and then reading the source code of
describe-function I discovered that it creates a "button" of a special type for links to function definitions:
zrgrep with this string ("
help-function-def") pointed me to
After all this digging around we can replace this button type with our own (note the comment in the code):
(define-button-type 'help-function-def :supertype 'help-xref 'help-function (lambda (fun file) (require 'find-func) (when (eq file 'C-source) (setq file (help-C-file-name (indirect-function fun) 'fun))) ;; Don't use find-function-noselect because it follows ;; aliases (which fails for built-in functions). (let ((location (find-function-search-for-symbol fun nil file))) (pop-to-buffer (car location)) (if (cdr location) (goto-char (cdr location)) (message "Unable to find location in file"))) (view-mode t)) ; <= new line: enable view-mode 'help-echo (purecopy "mouse-2, RET: find function's definition"))
As far as I can tell there is no function to add advice to: Emacs uses a
lambda here. On the other hand (as pointed out by @rationalrevolt) one can replace the
help-function property of the
help-function-def button type:
(require 'help-mode) (let ((help-func (button-type-get 'help-function-def 'help-function))) (button-type-put 'help-function-def 'help-function `(lambda (func file) (funcall ,help-func func file) (view-mode t))))
You can use directory-local variables to make Emacs' source files read-only by default. (See also C-hig
(emacs) Directory Variables RET).
Create a file called
.dir-locals.el at the root of the directory tree you wish to protect, with the following contents:
((nil . ((eval . (view-mode 1)))))
Edit: Michał Politowski points out in the comments that enabling
view-mode in this way is problematic, because when you dismiss the buffer with q it also disables the mode, meaning that the next time you visit that buffer
view-mode will not be enabled.
Edit 2: Constantine has provided a solution to that problem in the comments below:
((nil . ((eval . (when buffer-file-name (view-mode-enter nil #'kill-buffer))))))
This usefully adds a test to ensure that the buffer is already visiting a file, but the key change is the use of
view-mode-enter instead of
view-mode, as the former takes an
EXIT-ACTION argument which determines what to do when q is typed. In this case the exit action is to kill the buffer, ensuring that the next time the file is visited it will again end up in
Edit 3: Following that path, we can also see that the specified
EXIT-ACTION is ultimately passed to the
view-mode-exit function, and its docstring gives us an alternative solution:
view-no-disable-on-exit is a variable defined in `view.el'. Its value is nil Documentation: If non-nil, View mode "exit" commands don't actually disable View mode. Instead, these commands just switch buffers or windows. This is set in certain buffers by specialized features such as help commands that use View mode automatically.
Hence we can use the following:
((nil . ((eval . (when buffer-file-name (setq-local view-no-disable-on-exit t) (view-mode-enter))))))
I use the alternative approach which you can specify entirely in your init file (as opposed to creating a
.dir-locals.el file), and I simply make the files read-only rather than using
view-mode. My config looks like this:
;; Emacs (dir-locals-set-class-variables 'emacs '((nil . ((buffer-read-only . t) (show-trailing-whitespace . nil) (tab-width . 8) (eval . (whitespace-mode -1)))))) (dir-locals-set-directory-class "/usr/local/src/emacs" 'emacs) (dir-locals-set-directory-class "/usr/local/share/emacs" 'emacs) (dir-locals-set-directory-class "/usr/share/emacs" 'emacs)
Obviously you can do the same thing for your elpa directory, and any other directory which contains third-party source code.
I think all you need is to add a hook:
(add-hook 'find-function-after-hook 'view-mode)
This does not address your specific case, but the more general case of switching to
view-mode whenever you visit a source file from a help buffer. I'm offering it as an alternative to @Constantine's answer, since it wasn't readable as a comment.
I looks like I originally got this from the EmacsWiki.
(defadvice find-function-search-for-symbol (after view-function-source last (symbol type library) activate) "When visiting function source via Help, switch to view-mode" (with-current-buffer (car ad-return-value) (view-mode 1))) (defadvice find-variable-noselect (after view-var-source last (variable &optional file) activate) "When visiting variable source via Help, switch to view-mode" (with-current-buffer (car ad-return-value) (view-mode 1)))
Here's a solution that works for built-in documentation and an example showing how to extend it to ELPA. It works by matching the path to the current file against some regexes and applying
read-only-mode if any of them match.
Note that the buffer is read-only-ified if you visit it through
dired as well, not just through help.
I added a hook that runs after entering
emacs-lisp-mode that checks if the path to the file matches
/\.el\.gz$/, and applies read-only mode if it does.
(defun readonly-if-el-gz () (cond ((string-match "\\.el\\.gz\\'" (or (buffer-file-name) "")) (read-only-mode +1)))) (add-hook 'emacs-lisp-mode-hook 'readonly-if-el-gz)
Here's an example that checks ELPA too, using the heuristic that any path containing
.emacs.d/elpa is actually ELPA code.
(defun readonly-if-internal () (let ((name (or (buffer-file-name) ""))) (cond ((string-match "\\.el\\.gz\\'" name) (read-only-mode +1)) ((string-match "\\.emacs\\.d/elpa" name) (read-only-mode +1))))) (add-hook 'emacs-lisp-mode-hook 'readonly-if-internal)