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?

- 14,586
- 3
- 58
- 87

- 213
- 1
- 4
-
2Here is what I use to prevent accidental modification of any of my files that open in `emacs-lisp-mode` and I just do `C-x C-q` if I want to edit the source code. `(defun set-buffer-read-only () (setq buffer-read-only t)) (add-hook 'emacs-lisp-mode-hook 'set-buffer-read-only)` – lawlist Nov 18 '14 at 20:34
5 Answers
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 view-mode
.
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.

- 48,657
- 3
- 76
- 115
-
Great! This is clearly better than what I came up with. (What was I thinking of? I know about and use `.dir-locals.el` myself...) – Constantine Nov 18 '14 at 15:19
-
I've been something along the same lines based on a `find-file-hook` and a `read-only-dirs` list, but I like this approach. – glucas Nov 18 '14 at 15:35
-
This looks like a very clean approach. I have one minor problem though. With `((nil . ((eval . (view-mode 1)))))` what is the simplest way to make `View-quit` kill the buffers accessed via help? Otherwise after exiting the source view by pressing `q`, the buffer stays behind and when later accessing sources from the same file from help again view-mode is not started. – Michał Politowski Nov 18 '14 at 22:34
-
Michał Politowski: Right you are. I've updated the answer to incorporate that fact, but I don't have a workaround. Constantine's (edited) answer may be the best solution for using `view-mode`. – phils Nov 18 '14 at 23:30
-
1Today I figured out that I need a workaround for the issue pointed out by @MichałPolitowski --- and I found one: use `((nil . ((eval . (when buffer-file-name (view-mode-enter nil #'kill-buffer))))))` (note `(view-mode-enter ...)` instead of `(view-mode 1)`). This way pressing `q` kills the buffer and `view-mode` *is* enabled the next time I visit the same file. – Constantine Jan 23 '16 at 23:56
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.
By doing 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: help-function-def
.
Running zrgrep
with this string ("help-function-def
") pointed me to help-mode.el.gz
.
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))))

- 9,072
- 1
- 34
- 49
-
1I think I can try to use `button-type-get` and `button-type-put` to replace the lambda with my own that forwards to the existing lambda. – rationalrevolt Nov 18 '14 at 07:16
-
@rationalrevolt: Good idea! (Seems a bit fragile, but I suppose this is fragile too.) – Constantine Nov 18 '14 at 07:19
-
@rationalrevolt: Please see the updated answer. (Can't have newlines in comments, it seems...) – Constantine Nov 18 '14 at 08:11
-
Thanks! I was trying something similar but being a newbie to elisp I was bitten by the dynamic binding and couldn't get my closure to work :) – rationalrevolt Nov 18 '14 at 08:13
I think all you need is to add a hook:
(add-hook 'find-function-after-hook 'view-mode)

- 9,072
- 1
- 34
- 49

- 5,928
- 20
- 39
-
This is better than my monstrosity, but still does not really match the question: it turns on `view-mode` when navigating to *any* function using `C-h f`, not just Emacs sources. – Constantine Nov 18 '14 at 15:22
-
Experimentally, the help links don't actually run this hook. It looks like only the interactive `find-THING` commands utilise this hook, and the help buttons bypass it. – phils Nov 18 '14 at 23:27
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)))

- 20,175
- 1
- 51
- 83
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)

- 857
- 5
- 19