If you’re writing a lot of Common Lisp and you want to follow the convention of using all uppercase to refer to symbols in docstrings, comments etc., you really need something better than the shift key. Similarly if you’re writing C and you have VARIOUS_LONG_ENUMS.

The traditional way is a caps lock key. But that means giving up a whole keyboard key, all of the time, just for block capitalisation, which one hardly uses outside of programming. So a better alternative is to come up with some Emacs thing to get block capitalisation, as Emacs key binding is much more flexible than system keyboard layouts, and can let us get block capitalisation without giving up a whole key.

The simplest thing would be to bind some sequence of keys to just toggle caps lock. But I came up with something a bit fancier. With the following, you can type M-C, and then you get block caps until the point at which you’ve probably finished typing your symbol or enum name.

(defun spw/transient-caps-self-insert (&optional n)
  (interactive "p")
  (insert-char (upcase last-command-event) n))

(defun spw/activate-transient-caps ()
  "Activate caps lock while typing the current whitespace-delimited word(s).
This is useful for typing Lisp symbols and C enums which consist
of several all-uppercase words separated by hyphens and
underscores, such that M-- M-u after typing will not upcase the
whole thing."
  (interactive)
  (let* ((map (make-sparse-keymap))
     (deletion-commands '(delete-backward-char
                          paredit-backward-delete
                          backward-kill-word
                          paredit-backward-kill-word
                          spw/unix-word-rubout
                          spw/paredit-unix-word-rubout))
     (typing-commands (cons 'spw/transient-caps-self-insert
                            deletion-commands)))
     (substitute-key-definition 'self-insert-command
                                #'spw/transient-caps-self-insert
                                map
                                (current-global-map))
    (set-transient-map
     map
     (lambda ()
       ;; try to determine whether we are probably still about to try to type
       ;; something in all-uppercase
       (and (member this-command typing-commands)
            (not (and (eq this-command 'spw/transient-caps-self-insert)
                      (= (char-syntax last-command-event) ?\ )))
            (not (and (or (bolp) (= (char-syntax (char-before)) ?\ ))
                      (member this-command deletion-commands))))))))

(global-set-key "\M-C" #'spw/activate-transient-caps)

A few notes:

  • I have caps lock on left-ctrl on standard keyboard layouts, and on the sequence keypd-capslock-keypd on the Kinesis Advantage. These are about equally inconvenient, but good enough for those rare cases one needs caps lock outside of Emacs.

  • I also had the idea of binding this to Delete, because I don’t use that at all in Emacs, but Delete is relatively hard to hit on conventional keyboards, sometimes missing, and might not work in some text terminals.

  • I’ve actually trained myself to set the mark, type my symbol or enum and then just C-x C-u to upcase it, so I’m not actually using the above, but I thought someone else might like it, so still worth posting.

Update 7/xii/2024: I’m now using this simpler command:

(defun spw/backward-upcase-symbol (arg)
  (interactive "p")
  (let ((start (point)))
    (upcase-region (save-excursion
             (dotimes (_ (abs arg))
               (skip-syntax-backward "^w_")
               (skip-syntax-backward "w_"))
             (constrain-to-field nil start))
           start)))
(global-set-key "\C-cs" #'spw/backward-upcase-symbol)

It has the advantage of not requiring you to remember to set a mark before you start to type a symbol or enum.

Posted Sun Jun 20 00:26:03 2021