2024-01-22 From the kill ring to a register

I am probably one of the five or six people on this planet who use Emacs registers. I mostly use them for text insertion (especially when I need to insert some boilerplate – and especially more than one piece of it – several times, but it’s not general enough to be put into Yasnippet), but I sometimes store window configurations in them, and I might have used them for other purposes once or twice.

Sometimes, though, I kill some text to yank it elsewhere and only then realize that I’ll need to kill/yank some other fragments, too. Using the kill ring to do this one time is fine (with M-y or with browse-kill-ring); using it more times is rather inconvenient. Hence I decided to write a little command to copy the text from the kill ring to a specified register. And here it is.

(defun shorten-string (string length &optional suffix)
  "Shorten STRING to LENGTH and append SUFFIX if it is longer.
If LENGTH is too short, output the first character of STRING and
SUFFIX."
  (setq suffix (or suffix "..."))
  (if (<= (length string) length)
      string
    (format "%s%s"
            (substring string 0 (max (- length (length suffix))
                                     1))
            suffix)))

(defun copy-kill-to-register (register n)
  "Copy text from the kill ring to REGISTER.
With prefix argument N, use the Nth kill ring entry, but don't
move the last yank pointer to it."
  (interactive (let* ((n (prefix-numeric-value current-prefix-arg)))
                 (list (register-read-with-preview
                        (format
                         "Kill at position %s (%s) to register: "
                         n
                         (shorten-string (current-kill (1- n) t) 40)))
                       n)))
  (set-register register (current-kill (1- n) t)))

As you can see, I decided to be a bit fancy – for example, I display the text to be copied in the prompt, but make it no longer than 40 characters. It took me about half an hour to put all this together, but a working prototype (without the prompt shenanigans) took less than 10 minutes. This is the power of Emacs in action – modifying your Emacs to better meet your needs is a very frictionless experience, you just open some Elisp buffer, write a command, press C-M-x and it just works.

Note that the above code is not DRY – the (current-kill (1- n) t) fragment is repeated twice. The reason is that I wanted to have it in both the interactive clause and in the function body. As far as I know, there is no way to set a temporary variable so that it is available in both places. There are, however, two ways of circumventing this (neither very elegant). One is to declare a global variable for that purpose and setq it within he scope of interactive:

(defvar copy-kill-to-register-text nil
  "A variable set in `copy-kill-to-register'.
This is an internal variable `setq' in the `interactive' clause
of `copy-kill-to-register' so that it can be accessed later, in
the body of that command.")

(defun copy-kill-to-register (register n)
  "Copy text from the kill ring to REGISTER.
With prefix argument N, use the Nth kill ring entry, but don't
move the last yank pointer to it."
  (interactive (let* ((n (prefix-numeric-value current-prefix-arg)))
                 (setq text (current-kill (1- n) t))
                 (list (register-read-with-preview
                        (format
                         "Kill at position %s (%s) to register: "
                         n
                         (shorten-string text 40)))
                       n)))
  (set-register register text))

Definitely not elegant, but at least we don’t repeat ourselves. If we had some more complicated code (or more time-consuming to compute one, like a result of some complex calculations or of a network request, or one with side effects), this may be the way to go.

Another trick would be not to use interactive to get the value of text. We could set register to nil within interactive and then, if it is nil, ask the user for its value in the body of the function:

(defun copy-kill-to-register (register n)
  "Copy text from the kill ring to REGISTER.
With prefix argument N, use the Nth kill ring entry, but don't
move the last yank pointer to it."
  (interactive (list nil (prefix-numeric-value current-prefix-arg)))
  (let ((text (current-kill (1- n) t)))
    (setq register (or register
                       (register-read-with-preview
                        (format
                         "Kill at position %s (%s) to register: "
                         n
                         (shorten-string text 40)))))
    (set-register register text)))

This way, if our function is used in Elisp (and register is a character), it will just copy the n​th entry on the kill ring to the given register, and if used interactively, the interactive clause will set register to nil and register-read-with-preview will be evaluated. Still not beautiful, but at least it works (and we don’t have to type (current-kill (1- n) t) twice). It is up to you which variant to use – personally, I’ll still stick to the first one, and if I really wanted (or needed) to avoid evaluating the same expression twice, I would probably go with the “temporary global variable” variant.

As usual, let me remind you that if you want to learn to write code making your life in Emacs simpler, you should really read Introduction to programming in Emacs Lisp by the late Robert J. Chassell, and you may find my book, Hacking your way around in Emacs, a nice next step. And since I am currently working – slowly but steadily – on improving said book, if you have any suggestions, I might consider writing one more chapter!

CategoryEnglish, CategoryBlog, CategoryEmacs