Mac – How to get text key binding “yankAndSelect” to work

emacskeybindingskeyboardtext input

macOS's text input system has long supported custom keyboard shortcuts via ~/Library/KeyBindings/DefaultKeyBinding.dict. I have used this successfully for years to make text editing more Emacs-like, but there is one feature I have never been able to get to work: the yankAndSelect: method. It never cycles through the kill ring for me; it always only yanks the last-killed item. I am looking for help to make it work.

Here is how I tested it. First, I set NSTextKillRingSize using this command in a terminal:

defaults write -g NSTextKillRingSize -int 20

(I've also tried -string instead of -int in the command above, but it makes no difference in the behavior.) Next, I removed everything from my DefaultKeyBinding.dict file and left only this:

{
  "^y" = (yankAndSelect:);
}

Note that I leave controlk bound to its default text system action (which is to kill to the end of the line). Finally, I rebooted, just to be absolutely sure nothing unexpected persisted.

Based on many sources, such as Apple's documentation, the two documents on customizing the Cocoa text system and the list of selectors by Jacob Rus from 2006, Brett Terpstra's KeyBindings, and random other people's pages on the web, it seems that the following behavior should be seen (as described by Brett Terpstra):

  1. Open TextEdit (restart it if it was already open)
  2. Type a line of text, move the cursor to the start of the line, type controlk
  3. Write another line with different text, and repeat step 2
  4. Do it one more time
  5. On a blank line, type controly. This should paste the last line you killed
  6. Type controly again, and the pasted text should be replaced by the second line you killed
  7. Again, and you should see the first line. Continuing repetitions will cycle through the kill ring.

This is not what happens in my use. Instead, what happens is that step #6 does not paste the 2nd-to-last kill, but rather the most recent (3rd) kill – always. It is as if there is no kill ring at all, and yankAndSelect: always only yanks the most recent kill.

This is 100% repeatable behavior in every system (macOS 10.13, and an almost-fresh 10.14 VM) and every application that respects DefaultKeyBinding.dict (not only TextEdit) that I've tried.

What more must be done to get yankAndSelect: to cycle through the kill ring?

Best Answer

Update: It seems that on some versions of macOS, setting the configuration globally doesn't work; instead, use defaults write com.apple.TextEdit NSTextKillRingSize -string 6 (replacing com.apple.TextEdit with the desired application and 6 with the desired kill ring size) to set it on a per-app basis. Additionally, it appears that NSTextKillRingSize must be no greater than 16 or it will not work.


Original answer:

The issue appears to lie in how the defaults CLI handles the global defaults domain. Specifically, you want to write to the correct host's global defaults (i.e., yours), which it seems doesn't occur by default. (I still don't fully understand the intricacies of how all this works.) To accomplish this, replace your current defaults write command with the following:

defaults -currentHost write -g NSTextKillRingSize -string 6

This worked for me on Big Sur 11.1.