Capitalization keybinding

Have you seen Brett Terpstra’s latest keybinding thing? I don’t think I’ll ever use it, but it did inspire me to look into the way the Cocoa text system handles keybindings and add one particular keybinding that I’ve wanted for ages. Along the way I learned something new about TextMate.

Brett’s new keybinding sets ⌘⌥↩ (Command-Option-Return) to add the prefix for a Markdown list as you add items, the way TextMate and some other text editors do. The idea is you’re typing along like this:

* First item
* Second item|

When you get to the end of the second item, you type ⌘⌥↩ and the prefix for the next list item is inserted:

* First item
* Second item
* |

(In each example, the red bar represents the position of the insertion point.)

If you use hyphens as your list marker instead of asterisks, the keybinding will insert hyphens. If you use pluses, it’ll insert pluses. It can handle ordered lists, too.

As I said, several text editors already do something like this, so you might be wondering what the value is. The value is that Brett’s keybinding will work in any Cocoa text field, not just TextMate or Byword or nvAlt. So you can use it in Mail or Stickies or TextEdit most other text fields—it becomes a feature of the Cocoa text system itself, not of any particular editor.

Personally, I’d rather just type a regular ↩ and an asterisk rather than remember to type ⌘⌥↩ every time I’m making a list, so I won’t be installing this particular keybinding. But it did inspire me to spend a little time looking into keybindings to see if I could solve a longstanding typing problem of mine.

The problem is that my pinkies often lift off the Shift key just a tiny bit too soon, leaving words I intend to capitalize uncapitalized. I don’t realize it’s happened until I’m well into or even finished with the mistakenly uncapitalized word. What I need is a simple keystroke combination that capitalizes the word (or partial word) to the left of the insertion point.

I’ve created a macro to do just that in every text editor I’ve used regularly over the last ten years: NEdit on Linux, and BBEdit and TextMate on the Mac. In BBEdit and TextMate, the macros have been bound to ⌘⌃C, and over the years my fingers have become accustomed to reaching for those keys when I catch myself failing to capitalize a word. Unfortunately, I do a fair amount of writing outside my text editor—email, in particular—and TextMate macros don’t run in Mail. I can’t tell you how many times I’ve typed ⌘⌃C in Mail to no effect.

So I read this excellent introduction to Cocoa keybindings that Brett linked to. It’s by Jacob Rus, a familiar name from the TextMate mailing list, who also wrote this essential companion piece on the commands (or “selectors”) that you use with keybindings. If you want to make up your own keybindings, these are your bibles. Apple has documentation on this, but Jacob’s is easier to use. (It was also done in 2006, which puts him well ahead of all of us in recognizing the flexibility of the Cocoa text system.)

I then went to Brett’s GitHub repository of keybindings to see if he (or Lri, whose work with keybindings was the inspiration and basis of Brett’s) had a Capitalize keybinding already worked out. He did. It looked like this:

{
  "~." = (capitalizeWord:, moveRight:, moveRight:);
}

I changed it to

{
  "@^c" = (capitalizeWord:, moveRight:, moveRight:);
}

so the trigger would be ⌘⌃C rather than the ⌥. that Brett had.1 and saved it to ~/Library/KeyBindings/DefaultKeyBinding.dict.

I wasn’t really happy with the way this worked, because it wouldn’t capitalize the preceding word if I’d already typed a space after it, something that happens quite often. I decided to take a look at my TextMate Capitalize macro to see if I could translate it into Cocoa text commands. As it turned out, no translation was needed.

TextMate Capitalize macro

The TextMate macro language is the Cocoa text language, so all I had to do was take those commands and format them for a plist:

{
  "@^c" = (moveWordLeftAndModifySelection:, capitalizeWord:, moveRight:);
}

This is a handy thing to know because TextMate creates macros by recording your actions. If you have a sequence of keystrokes and text entries you’d like to turn into a Cocoa keybinding, you can simply run through those actions in TextMate while recording a macro. When your done recording, the macro listing will give you what you need to put into your DefaultKeyBinding.dict.

As it happened, this keybinding worked great in Stickies, but not in Mail. For some reason, in Mail the moveRight: command doesn’t leave the insertion point where it started but pushes it one extra character position to the right.2 Also, if the text is like this,

hello how |are you

invoking the keybinding will turn it into this in Stickies,

hello How |are you

but this in Mail,

hello How Are |you

capitalizing two words instead of one and moving the cursor past the end of the second. Not at all what I want.3

I think this difference in behavior arises because applications are allowed implement these commands in slightly different ways—at least, that’s my impression from the Apple documentation.

So I dug back into Jacob’s instructions and came up with a keybinding that works the same way in both Stickies4 and Mail:

{
  "@^c" = (setMark:, moveWordLeft:, moveRight:,
          capitalizeWord:, moveLeft:, swapWithMark:);
}

The setMark: and swapWithMark: commands ensure that the insertion point ends up where it started. And the moveWordLeft:, moveRight: dance prevents Mail from grabbing the word immediately to the right of the cursor and capitalizing it, too.

So the good news is I have a capitalization macro that works the way I want in Mail, Stickies, and my Twitter client, Dr. Twoot. Apart from TextMate, these are the applications in which I do most of my typing, so it’s a been a big help even in just the day or two I’ve been using it.

The bad news is that it doesn’t work well at all in Safari text fields. In the most basic case it will capitalize the word immediately to the left of the insertion point, but it leaves the capitalized word selected, which makes it all too easy to delete by typing a space. And in more complicated situations, like the one above with the insertion point between words, it capitalizes the wrong word and leaves it selected—two errors for the price of one.

(This is a not-so-subtle plea for Brett or Lri to give me a hint on how to fix it.)


  1. A description of the syntax for the trigger keys is given in Jacob’s essay. Suffice it to say that @ means ⌘, ~ means ⌥, and ^ means ⌃. 

  2. If there is a character to the right of where it started, which there is if I’m typing along in the middle of a document. 

  3. Why I would want the keybinding to work when the insertion point is at the front of the word after the one I want to capitalize? Because sometimes I type into the next word before realizing I missed the capitalization. In those instances, I ⌥← back to the beginning of the second word and then hit ⌘⌃C. 

  4. Yes, I do use Stickies. It’s a convenient place to throw text that I don’t need right now but expect to need later in the day. Also, I can double-click on its top bar to windowshade it out of the way. 


3 Responses to “Capitalization keybinding”

  1. Lri says:

    A few issues with the macro at the end of the post (setMark:, moveWordLeft:, moveRight:, capitalizeWord:, moveLeft:, swapWithMark:):

    • It can’t be used with selections
    • In TextMate it applies to the current paragraph (not word)
    • aa -⁁bb is converted to Aa -⁁bb
    • When triggered at the start of a paragraph, it applies to the last word of the previous paragraph

    I couldn’t really come up with anything better than this: selectWord:, capitalizeWord:, moveForward:. It solves all of the above, but none of these though:

    • Changes word1 ⁁word2 to word1 Word2⁁
    • Doesn’t capitalize word1 in word1⁁ word2
    • Words stay selected in Safari
    • The caret moves extra characters forward in Mail
  2. sapporo says:

    Reminds me of playing with TextExtras years ago. Perfect tool to fine tune the Cocoa text system, but, unfortunately, relies on the (now deprecated) Input Manager to do its magic.

  3. Dr. Drang says:

    Lri,

    The first problem in your list is the only one that bothers me, and given how I use it, it’s only a minor annoyance. In principle, I wish it capitalized a selected word, but in practice I’ve never wanted to use it that way.

    The TextMate problem is solved by the fact that I’ve never deleted my old capitalize macro, and within TextMate it overrides the Cocoa keybinding.

    The third and fourth problems aren’t problems to me—that’s how I want it to behave.

    The problem with Safari not deselecting text when issued a moveRight: or moveLeft: is both perplexing and annoying, but if even you haven’t figured it out, I won’t waste any more time trying.