More keybindings: Text editing shortcuts
I guess it’s keybinding week this week. I’ve talked about the kill ring and repeat binding, but I’ve been digging in and revamping my own DefaultKeyBinding.dict
file, so I keep wanting to share some of the cooler things it can do because I know the file in the project is pretty massive and hard to parse.
There are a ton of text editing shortcuts in my bindings (that’s basically their sole purpose), but I wanted to highlight just a few that I use so often I don’t even think about it.
TextMate Command-Return
First, ⌘⏎. TextMate was the first place I ever saw this, but now it’s pretty standard in most text editors, from Byword to nvUltra, VS Code to Sublime. When you hit ⌘⏎, it moves your cursor to the end of the current line/paragraph and inserts a new line, no matter where you are in the paragraph. And ⇧⌘⏎ will do the same but before the current line/paragraph. It allows you to be editing in the middle of a graf, then just hit ⌘⏎ to start typing again, saving use of the mouse or arrow keys to get to the new insertion point.
This keybinding will work in any Cocoa text field on your Mac, so it’s no longer limited to your favorite coding app. It works in TextEdit, Mail, Pages (but not Word), and in apps like Bear, Day One, or NotePlan. Just about anywhere that uses native text fields.
The first version I’ll show you is the most universal. The reason for this is that ⌘⏎ is frequently used by apps for special purposes, and the app’s keybindings will override your system-wide keybindings. So having a less-common shortcut makes this more available. I actually include both versions in my .dict
file, and when I’m in an app that overrides ⌘⏎, I just switch to using this version.
I use ⌥O for these, but note that doing so will mean you can’t just type the slashed-O symbol (
Ø
) anymore1. If you never type that symbol, then you’re fine, but if you do use it, you might want to consider a different shortcut.
To use any of the examples in this post, you’ll just add them to
~/Library/KeyBindings/DefaultKeyBinding.dict
, a directory and text file you can create if it doesn’t exist. The examples include the outer curly brackets, but if you’re adding to an existing set of keybindings, only copy the portion inside of the curly brackets and place it within the existing outer curly brackets in your file.If you’re combining multiple examples from this post, you only want one set of curly brackets at the root, and then the contents can be appended within those curly brackets.
Here are the bindings using ⌥O as the shortcut:
{
// blank line after current (Option-O)
"~o" = (moveToEndOfParagraph:, insertNewline:);
// blank line before current (Option-Shift-O)
"~O" = (moveToBeginningOfParagraph:, moveLeft:, insertNewline:);
}
And here are the same bindings using ⌘⏎:
{
// TextMate Command-Return (Command Return)
"@\U000D" = (moveToEndOfParagraph:, insertNewline:);
// Insert blank line above paragraph (Command Shift Return)
"@$\U000D" = (moveToBeginningOfParagraph:, moveLeft:, insertNewline:);
}
Remember that any time you edit the
DefaultKeyBinding.dict
file, you have to restart open apps for the new bindings to be recognized.
Moving lines around
This is another binding that’s quite common in more advanced editors: the ability to move the current line up or down, and to increase/decrease indentation. In Sublime Text these are bound to ⌃⌘-Arrow keys. I use the Sublime bindings in VS Code, so I’m not sure what they are there by default. But It’s so nice to have these in any app, you can bind them to whatever shortcuts make sense to you. They won’t override the native bindings in your favorite apps, so you can duplicate whatever you use most.
With these installed, you can be anywhere in a paragraph and move the entire graf up and down, and back and forth. In keybinding terminology, a paragraph is any text that has a line break or BOF at its start, and ends in a line break. So in a Markdown unordered list, each list item is a paragraph.
{
// > Line motion with arrow keys
// move line up (Control-Command-Up Arrow)
"^@\UF700" = (selectParagraph:, setMark:, deleteToMark:, moveLeft:, moveToBeginningOfParagraph:, yank:, moveLeft:, selectToMark:, moveLeft:);
// move line down (Control-Command-Down Arrow)
"^@\UF701" = (selectParagraph:, setMark:, deleteToMark:, moveToEndOfParagraph:, moveRight:, setMark:, yank:, moveLeft:, selectToMark:);
// indent line (Control-Command-Right Arrow)
"^@\UF703" = (setMark:, moveToBeginningOfParagraph:, insertText:, "\t", swapWithMark:, moveRight:);
// outdent line (one tab or char) (Control-Command-Left Arrow)
"^@\UF702" = (setMark:, moveToBeginningOfParagraph:, moveRight:, deleteBackward:, swapWithMark:, moveLeft:);
}
In case you were wondering, the list items in the animation were generated using the Increment Templated service.
Note that these bindings aren’t very smart or context-aware. If you try to outdent a paragraph that’s already at the left border, it will instead remove the first character. And it doesn’t respect newlines between paragraphs very well, so if you have three paragraphs with newlines between them, and then attempt to move the last one to the second spot, you’ll need to add back the newline after moving. Which is where the TextMate bindings above come in super handy.
They also use a tab for indentation, which works great for outdenting lines that are indented with tabs. If, however, the indentation is spaces, the outdent binding will only delete one space at a time.
If you’re already used to Vim keybindings, you may want to try the same setup using hjkl. I’ve found that these combinations conflict with a few different apps’ global shortcuts on my own system, but your mileage may vary. The arrow key version works everywhere for me, so this is just an example of alternative bindings.
{
// move line up
"^@k" = (selectParagraph:, setMark:, deleteToMark:, moveLeft:, moveToBeginningOfParagraph:, yank:, moveLeft:, selectToMark:, moveLeft:);
// move line down
"^@j" = (selectParagraph:, setMark:, deleteToMark:, moveToEndOfParagraph:, moveRight:, setMark:, yank:, moveLeft:, selectToMark:);
// indent line
"^@l" = (setMark:, moveToBeginningOfParagraph:, insertText:, "\t", swapWithMark:, moveRight:);
// outdent line (one tab or char)
"^@h" = (setMark:, moveToBeginningOfParagraph:, moveRight:, deleteBackward:, swapWithMark:, moveLeft:);
}
Check out the KeyBindings project for more!
-
This will likely only be a problem for people writing in Danish, Norwegion, Faroese, or Southern Sámi languages. ↩