How to advise primitives in Emacs

2019-06-25 07:51发布

问题:

I was trying to answer another SO question when I hit upon some very odd behavior. Here's my little test case:

(make-variable-buffer-local
 (defvar my-override-mode-on-save nil
   "Can be set to automatically ignore read-only mode of a file when saving."))

(defadvice file-writable-p (around my-overide-file-writeable-p act)
  "override file-writable-p if `my-override-mode-on-save' is set."
  (or
   my-override-mode-on-save
   ad-do-it))

(defun my-override-toggle-read-only ()
  "Toggle buffer's read-only status, keeping `my-override-mode-on-save' in sync."
  (interactive)
  (setq my-override-mode-on-save (not my-override-mode-on-save))
  (toggle-read-only))

(defun tester-fn ()
  (interactive)
  (let ((xxx (file-writable-p "/tmp/foofoo"))
        (yyy (file-writable-p "/tmp/fooxxfoo")))
    (message (concat "XXX: " (if xxx "yes" "no") "   -   YYY: " (if yyy "yes" "no")))))

where:

  • /tmp/foofoo is a read-only file that I've visited and run my-override-toggle-read-only in.
  • /tmp/fooxxfoo does not exist.
  • /tmp is writable by the user I'm logged in as.

If I run tester-fn in a buffer where my-override-mode-on-save is set to t then I get an unexpected result: XXX: no - YYY: no. If I run tester-fn while in some other buffer (e.g. scratch) I get the expected response in the minibuffer: XXX: no - YYY: yes. Tracing the advice through the debugger shows it to be doing exactly what I think it should be doing, executing the parts I expect it to, skipping the parts I expect it to, returning the value I expect it to. However, tracing tester-fn through the debugger shows very different values being returned (nil & t if the variable evaluates as nil, nil & nil if the variable evaluates as non-nil). The nil & nil return is really what I find bizarre.

I have no clue what's happening here. Anyone know why I'm not getting the results I expect?

回答1:

Your code looks good, except the one missing key. You need to set the return value appropriately:

(defadvice file-writable-p (around my-overide-file-writeable-p act)
  "override file-writable-p if `my-override-mode-on-save' is set."
  (setq ad-return-value
        (or
         my-override-mode-on-save
         ad-do-it)))

This is documented in the advice manual.