0

I have a working snippet that advices both kill-buffer and kill-this-buffer to not kill the scratch buffer:

  (defun ninrod/scratch-bodyguard (buffer-assassin &rest arguments)
    (let ((buffer-to-kill (buffer-name (current-buffer))))
      (if (equal buffer-to-kill "*scratch*")
          (message "DENIED! don't kill my precious *scratch*!!")
        (apply buffer-assassin arguments))))
  (defun ninrod/scratch-protection (buffer-assassin &rest arguments)
    (let ((buffer-to-kill (car arguments)))
      (if (equal buffer-to-kill "*scratch*")
          (message "DENIED! don't kill my precious *scratch*!!")
        (apply buffer-assassin arguments))))
  (advice-add #'kill-this-buffer :around #'ninrod/scratch-bodyguard)
  (advice-add #'kill-buffer :around #'ninrod/scratch-protection)

The problem is that these lines:

  (message "DENIED! don't kill my precious *scratch*!!")
 (apply buffer-assassin arguments))))

Are repeated in both functions, so I thought that I could apply the DRY principle and refactor the snippet to this:

(defun ninrod--protection (buffer-assassin buffer-to-kill &rest arguments)
  (if (equal buffer-to-kill "*scratch*")
      (message "DENIED! don't kill my precious *scratch*!!")
    (apply buffer-assassin arguments)))
(defun ninrod/scratch-bodyguard (buffer-assassin &rest arguments)
  (let ((buffer-to-kill (buffer-name (current-buffer))))
    (ninrod--protection 'buffer-assassin buffer-to-kill arguments)))
(defun ninrod/scratch-protection (buffer-assassin &rest arguments)
  (let ((buffer-to-kill (car arguments)))
    (ninrod--protection 'buffer-assassin buffer-to-kill arguments)))
(advice-add #'kill-this-buffer :around #'ninrod/scratch-bodyguard)
(advice-add #'kill-buffer :around #'ninrod/scratch-protection)

This causes all hell to break loose. Now I can't even close emacs, because apparently emacs tries to kill all buffers and as I've just tampered with the kill buffer functions, well, it's bad. Very bad.

I know I mean well, but I'm must be doing something very stupid. For starters, I don't know if I can really pass around functions as parameters? So it could be that?

How would you refactor that snippet to apply the dry principle?

edit:

this works:

(defun ninrod--protection (buffer-assassin buffer-to-kill &rest arguments)
  (if (equal buffer-to-kill "*scratch*")
      (message "DENIED! don't kill my precious *scratch*!!")
    (apply buffer-assassin arguments)))

(defun ninrod/scratch-bodyguard (buffer-assassin &rest arguments)
  (let ((buffer-to-kill (buffer-name (current-buffer))))
    (ninrod--protection 'buffer-assassin buffer-to-kill arguments)))

(advice-add #'kill-this-buffer :around #'ninrod/scratch-bodyguard)

so the problem lies exactly here:

(defun ninrod/scratch-protection (buffer-assassin &rest arguments)
  (let ((buffer-to-kill (car arguments)))
    (ninrod--protection 'buffer-assassin buffer-to-kill arguments)))

edit2: quoting or unquoting the function buffer-assassin does not make a difference

  • 2
    Regarding edit2: It does make a difference. Quoted, you get a Symbol's function definition is void: buffer-assassin error, unquoted it's Wrong type arguments: stringp, (#<buffer whatever>). The second error is due to the &rest that @YoungFrog mentioned. As he/she said, there are different types of hell. – ChrisR Feb 10 '17 at 17:21
  • Yes, I should have stated the error I was getting, thank you – ninrod Feb 10 '17 at 17:54
  • When that happen to me I do Cx-b *scratch* RET to recreate the *scratch* buffer. – Eclip Sedegazpach Feb 15 '17 at 7:08
5

Don't quote buffer-assassin when passing it to ninrod--protection. In your advice-functions, buffer-assassin is a variable, the value of which is the originial function. When you pass it quoted, buffer-assassin in ninrod--protection will have the value 'buffer-assassin (i.e. the symbol itself), so apply will try calling a (global) function named like that, not the value of the caller's local variable.

Edit: A somewhat cleaner solution (i.e. without advising) would be

(add-to-list 'kill-buffer-query-functions 
  (lambda () (not (eq (current-buffer) (get-buffer "*scratch*")))))
  • that's nice, but unquoting buffer-assassin sill causes all hell to break loose. – ninrod Feb 10 '17 at 16:13
  • 3
    I guess removing &rest in your ninrod--protection function will help. Problem is 'arguments' is transmitted as a list, and &rest makes it a list of a list. – YoungFrog Feb 10 '17 at 16:40
  • Additionally hell can break loose in so many ways. It would be nice to include the error message or a backtrace when that happens. – YoungFrog Feb 10 '17 at 16:41
  • @YoungFrog, removing &rest from the ninrod--protection function fixed the problem! – ninrod Feb 10 '17 at 16:51
  • 1
    Additionally, you should make sure to actually make buffer-to-kill a string in ninrod/scratch-protection, as kill-buffer may also be called on a buffer directly, which makes your comparison to "*scratch*" fail. Try (kill-buffer (get-buffer "*scratch*")) to see where things go wrong. – ChrisR Feb 10 '17 at 16:56
0

Edit: I misread the code, go with ChrisRs answer.

You can definitely pass around functions, but you do it indirectly by passing the symbol the function is stored in. When you write:

(f 'myfunction)

You are passing the symbol 'myfunction as an argument to the function f. Specifically when you intend to pass a symbol in order to pass a function, you should actually write #'myfunction because that hints to emacs you're intending to pass a function and you get better error messages. Emacs is a lisp-2, meaning that a single symbol can hold both a variable value and a function value, but generally if a function takes a "function" as an argument, it will assume that for the symbol you pass in you intend to use the function value associated with it, not the variable value, and will extract it for you.

  • I think it's the other way around: the original function is stored in the value slot of the advice's first parameter. That's why the apply in the original implementation works. – ChrisR Feb 10 '17 at 12:49
  • @ChrisR oops, yes you are correct – Joseph Garvin Feb 10 '17 at 13:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.