Skip to content

Instantly share code, notes, and snippets.

@jdtsmith
Last active July 11, 2024 23:23
Show Gist options
  • Save jdtsmith/f41207cb0ddc7579ed648af1f69e2a0a to your computer and use it in GitHub Desktop.
Save jdtsmith/f41207cb0ddc7579ed648af1f69e2a0a to your computer and use it in GitHub Desktop.
custom-buffer-local-keys
;; JD Smith 2024, based on https://www.emacswiki.org/emacs/BufferLocalKeys
(defvar-local custom-buffer-local-keys nil
"Key-bindings to be set up local to the current buffer.
A single (KEY . BINDING) cons or list of such conses, of the form
`bind-keys' accepts. Set this as a file-local variable to make
bindings local to that buffer only.")
;; Only include this if you trust the files you open
(put 'custom-buffer-local-keys 'safe-local-variable 'consp)
(defvar-local my/custom-buffer-local-map nil)
(defun my/process-custom-buffer-local-keys ()
"Setup and enable a minor mode if custom-buffer-local-keys is non-nil."
(when (and (boundp 'custom-buffer-local-keys) custom-buffer-local-keys)
(let ((map my/custom-buffer-local-map)
(keys custom-buffer-local-keys))
(unless map
(setq map (make-sparse-keymap))
(set-keymap-parent map (current-local-map))
(use-local-map (setq my/custom-buffer-local-map map)))
(unless (consp (car keys)) (setq keys (list keys)))
(dolist (k keys) (local-set-key (kbd (car k)) (cdr k))))))
(add-hook 'hack-local-variables-hook #'my/process-custom-buffer-local-keys)
@jdtsmith
Copy link
Author

jdtsmith commented Jun 13, 2024

If you'd like to define some keys locally to a single buffer only, this is how to do it. Include the above in your init, then set the file-local variable custom-buffer-local-keys to a cons or list of conses of the form bind-keys would take (which is the same form as the :bind stanza of use-package). These bindings will be automatically enabled locally for that buffer only.

An example, at the end of an org file:

* Local Variables :noexport:
#+begin_src emacs-lisp
Local Variables:
custom-buffer-local-keys: (
("<f9>" . (lambda () (interactive) (my/org-babel-execute) (outline-next-heading)))
("<f8>" . (lambda () (interactive) (my/org-babel-execute) (outline-previous-heading))))
End:
#+end_src

@alphapapa
Copy link

That's a nice way to do it. In the past I put a source block in the file that called local-set-key and then evaluated that block with an eval line in the local variables.

@jdtsmith
Copy link
Author

Yeah the key good idea on the wiki was to use a custom buffer-derived minor mode. local-set-key has always seemed poorly named, since the (current-local-map) is usually eq to (mode-map), i.e. the key would be set in all org buffers. I'll probably use this to allocate the same function keys to notional "action, previous, next" commands and have those dtrt in various files. Of course leaving <f10> for activities-resume ;).

@mekeor
Copy link

mekeor commented Jun 27, 2024

In #emacs-channel on irc.libera.chat IRC-network, bpalmer shared these insights:

  • a buffer struct literally has a member that is its keymap. The typical behavior of a major mode is (use-local-map the-map-that-the-mode-defined)
  • if you want a unique keymap, M-: (use-local-map (make-sparse-keymap)) RET or (what eshell did, iirc) , (use-local-map (copy-keymap eshell-mode-map))
  • at that point, local set key will affect the object that is the buffer's keymap.
  • The typical usage, remember, is (use-local-map my-mode-map), so mutating that map (like define-key does) will make the changes visible in every mode.
  • in every buffer that uses that object.
  • But that's just due to mutable objects.
  • note another approach is to make a new keymap and use keymap-set-parent to the original mode map, rather than make a copy.
  • that way any local-set-key affects only the current buffer, but define-key to the mode map (or local-set-key in a buffer using the mode map) will still affect the current buffer.

@jdtsmith
Copy link
Author

jdtsmith commented Jun 28, 2024

Interesting, thanks for sharing that. I came up with a simplified version based on these ideas.

@jm-g
Copy link

jm-g commented Jul 10, 2024

A very cool idea, that I added immediately to my emacs config. But adding custom-buffer-local-keys to safe-local-variables is questionable. This could be used to trick someone into running untrusted code. The example shows how to define a custom command inline. Just use that to prepend self-insert-command with something else and bind it to a simple letter key.

@jdtsmith
Copy link
Author

Yes you could obviously craft a malicious file that changed a common binding like SPACE to do something nefarious, if you could convince me to download and open that file. There are many ways to do this, and I really doubt this particular custom config would invite targeted attacks.

I've added a warning comment. If you worry about this, you could change the name of the local variable to something distinct like your-initials//custom-buffer-local-keys to limit possible misuse (and don't tell anyone the name you chose).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment