KeyBindings: new, improved “surround” commands

[Tweet : ADN : nvALT]

Post header keyboard image

As the next part in the keybindings series I’m demonstrating some improvements I’ve made to the original “surround” commands since my first time around. This set of commands is designed to wrap selected text in a variety of paired characters. The keys are the same, but the commands now work inside of single-line text fields (like you often find in Safari), prevent auto-pairing in apps like nvALT, MultiMarkdown Composer and Byword, and a few refinements to cursor positioning.

Here’s the code to drop into ‘DefaultKeyBinding.dict’, located at ~/Library/DefaultKeyBinding.dict (create the folder and/or the file if you don’t have one1):

  "^@s" = {   // surround commands
    // wrap () with spaces
    "(" = (delete:, insertText:, "( ", yank:, insertText:, " ", moveLeft:, insertText:, " )", deleteForward:);
    // wrap () no spaces
    ")" = (delete:, insertText:, "( ", deleteBackward:, yank:, insertText:, " ", moveLeft:, insertText:, ")", deleteForward:);
    // wrap [] with spaces
    "[" = (delete:, insertText:, "[ ", yank:, insertText:, " ", moveLeft:, insertText:, " ]", deleteForward:);
    // wrap [] no spaces
    "]" = (delete:, insertText:, "[ ", deleteBackward:, yank:, insertText:, " ", moveLeft:, insertText:, "]", deleteForward:);
    // wrap {} with spaces
    "{" = (delete:, insertText:, "{ ", yank:, insertText:, " ", moveLeft:, insertText:, " }", deleteForward:);
    // wrap {} no spaces
    "}" = (delete:, insertText:, "{ ", deleteBackward:, yank:, insertText:, " ", moveLeft:, insertText:, "}", deleteForward:);
    // wrap <> with spaces
    "<" = (delete:, insertText:, "< ", yank:, insertText:, " ", moveLeft:, insertText:, " >", deleteForward:);
    // wrap <> no spaces
    ">" = (delete:, insertText:, "< ", deleteBackward:, yank:, insertText:, " ", moveLeft:, insertText:, ">", deleteForward:);
    // wrap single quotes
    "'" = (delete:, insertText:, "' ", deleteBackward:, yank:, insertText:, " ", moveLeft:, insertText:, "'", deleteForward:);
    // wrap backticks
    "`" = (delete:, insertText:, "` ", deleteBackward:, yank:, insertText:, " ", moveLeft:, insertText:, "`", deleteForward:);
    // wrap double quote
    "\"" = (delete:, insertText:, "\" ", deleteBackward:, yank:, insertText:, " ", moveLeft:, insertText:, "\"", deleteForward:);
  };

Key: ^=control, @=command

The whole set is bound to Control-Command-S, so you use them by: * selecting some text that needs quotes, parenthesis, brackets, etc. wrapped around it * press Control-Command-S * typing the key you want to wrap the text in

It’s set up to handle parenthesis, square brackets, angle brackets, curly brackets, single/double quotes and backticks. In the parenthesis and brackets, typing the left character of the pair adds the wrapping with spaces on the inside of the pair (e.g. ( text I wrapped )) and the right character wraps with no spaces (e.g. (text I wrapped)).

If you just want one or two and don’t need them bundled in the Control-Command-S wrapper, you can always extract them from that section of the plist and move them into the root. Because we’re just wrapping and not autopairing, the commands make more sense (to me) with a hotkey to enable them.

If you don’t have a selection when you run these, it’s going to insert the last thing it “yanked” and you never know what you’ll get. The yank pasteboard is separate from the main pasteboard, so it has little to do with what you intentionally copied last. If you’re using these keybindings, it will likely contain the last selection you ran one of them on.

Just for edification, let’s dissect one, shall we? Here’s the binding for wrapping in double quotes, made a little prettier for readability:

// wrap double quote
"\"" = (	delete:, 
			insertText:, "\" ", 
			deleteBackward:, 
			yank:, 
			insertText:, " ", 
			moveLeft:, 
			insertText:, "\"", 
			deleteForward:, 
	);
  • In the first three line delete the selection. The text is now in our yank pasteboard.
  • Then we insert the text “” “
    • The backslash escapes the double quote so it doesn’t break the surrounding quotes in the plist
    • The trailing space fools most auto-pairing setups into not inserting the right-side pairing automatically. We immediately delete backward to remove the extra space
  • Next, we use the “yank:” method to put back the text we originally cut out and insert a space immediately after it. The space allows us to manipulate the end of the text without hitting the end of the line, as would happen in certain editors and always in single-line text fields.
  • We move left past the new space and insert the closing quote
  • Finally, we delete to the right remove the trailing space

We want the cursor to end up after the last character of the wrapped text, so we can just stop there.

Hopefully that gives you enough ammo to start coming up with more, and enough of a taste to help you realize that this is seriously the most powerful text tool in your arsenal. It’s application agnostic, completely customizable and totally free. Have fun.

For my full DefaultKeyBinding.dict file, check out the GitHub repo and feel free to steal/fork mine. I’ll continue posting and describing pieces of it, but if you’re impatient and know how to read it already, go for it.

  1. If you’re creating a new DefaultKeyBinding.dict file, be sure to add a pair of curly brackets ({}) in the blank file and insert any new bindings between them.