2

I am trying to execute the code here, which I copy below

    (defun buffer-local-set-key (key func)
      (interactive "KSet key on this buffer: \naCommand: ")
      (let ((name (format "%s-magic" (buffer-name))))
        (eval
         `(define-minor-mode ,(intern name)
            "Automagically built minor mode to define buffer-local keys."))
        (let* ((mapname (format "%s-map" name))
               (map (intern mapname)))
          (unless (boundp (intern mapname))
            (set map (make-sparse-keymap)))
          (eval
           `(define-key ,map ,key func)))
        (funcall (intern name) t)))

However, when executing it like this

(buffer-local-set-key "C-x k" #'previous-buffer)

I am getting an error eval: Symbol’s value as variable is void: func. I suspect it is at the line (eval `(define-key ,map ,key func)))

I understand what the code is doing, but I am not sure why eval is being used here instead of the function being called directly, i.e. (define-key map key func). However, when I tried doing that instead I got the error

Wrong type argument: keymapp, buf-magic-map

Can someone explain to me why eval might be needed here? And how to fix the error above?

I have lexical-binding set to true in case that was relevant.

1 Answer 1

3

There are several things wrong with the code:

  • You need #', (or just ',) in front of func. Inside a backquote expression, just func would result in the literal symbol func, not its value as a variable. Use comma (, to evaluate it. But then quote that evaluated result.
  • You need to use kbd, or else you are trying to bind the key sequence C-x SPC k.
  • You need to lose the t arg when calling the minor-mode function. The minor mode is enabled when no arg is passed. See the Emacs manual, node Minor Modes:

    "If the mode command is called via Lisp, the minor mode is unconditionally turned on if the argument is omitted or nil."

(defun buffer-local-set-key (key func)
  (interactive "KSet key on this buffer: \naCommand: ")
  (let ((name (format "%s-magic" (buffer-name))))
    (eval `(define-minor-mode ,(intern name)
             "Automagically built minor mode to define buffer-local keys."))
    (let* ((mapname (format "%s-map" name))
           (map (intern mapname)))
      (unless (boundp (intern mapname))
        (set map (make-sparse-keymap)))
      ;; Use `,' before FUNC, then quote the result of the evaluation.
      (eval `(define-key ,map ,key #',func)))
    ;; Lose the `t' arg from (funcall (intern name) t))).
    (funcall (intern name))))

;; Use `kbd'
(buffer-local-set-key (kbd "C-x k") 'previous-buffer)

The reason you need to use eval is that the result of the backquote expression is a list, that is, it's the resulting code in list form. You then need to evaluate that code (i.e., that list).

UPDATE after your comment question of why use backquote at all for the second occurrence

Yes, the second occurrence doesn't need eval and backquote. But you then need to use symbol-value to get the value of variable map.

(defun buffer-local-set-key (key func)
  (interactive "KSet key on this buffer: \naCommand: ")
  (let ((name (format "%s-magic" (buffer-name))))
    (eval `(define-minor-mode ,(intern name)
             "Automagically built minor mode to define buffer-local keys."))
    (let* ((mapname (format "%s-map" name))
           (map (intern mapname)))
      (unless (boundp (intern mapname))
        (set map (make-sparse-keymap)))
      ;; Use `,' before FUNC, then quote the result of the evaluation.
      (define-key (symbol-value map) key func)) ; <== Use `symbol-value'
    ;; Lose the `t' arg from (funcall (intern name) t))).
    (funcall (intern name))))

2
  • Thanks! I understand that eval evaluates a list which is constructed in the function above. But my question was why one would use (eval (define-key ,map ,key #',func))` instead of (define-key map key func) directly. Is there an advantage to using eval?
    – Tohiko
    Commented Mar 10, 2020 at 20:21
  • Updated answer to address your comment question. See also this Q&A, for using backquote with comma, to insert an evaluated value.
    – Drew
    Commented Mar 10, 2020 at 22:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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