Multiple Carets

One of the features user have continually requested for TextMate is the ability to insert multiple carets. Allan, TextMate's developer, was always a little resistant to the idea because it seemed to require the use of a mouse and he's not a mouse guy. Well, he caved. You can now use a mouse to throw down multiple carets and then edit away.

In this article will discuss:

  • Using multiple carets with the mouse
  • Changes to column selection
  • Using multiple carets with Find
  • Using multiple carets with Copy and Paste

Using the Mouse

Let's look at an example. Pretend we're building some sort of IO filter. It allows you to wrap a file or some other stream and read data from it, but that data is modified on the way in. In Ruby, you might have some code like:

class IOFilter
  # ...

  def gets(*args)
    filter(@io.gets(*args))
  rescue IOError => error
    handle_error(error)
  end

  def read(*args)
    filter(@io.read(*args))
  rescue IOError => error
    handle_error(error)
  end

  # ...
end

Rescuing IOError covers some of the things that could go wrong. For example, if the @io object we're wrapping was opened for writing but not reading, an IOError will be tossed. There are other errors that can be raised for read operations though. For example, a socket might raise an EOFError when the input is exhausted. Let's say we received a bug report and need to add some handling for that error as well. Assuming our handle_error() method is up to the task, we just need to add the new error in two places. Here's where we would like to have our carets (shown with ‸):

class IOFilter
  # ...

  def gets(*args)
    filter(@io.gets(*args))
  rescue IOError‸ => error
    handle_error(error)
  end

  def read(*args)
    filter(@io.read(*args))
  rescue IOError‸ => error
    handle_error(error)
  end

  # ...
end

TextMate 2 makes it trivial to do just that. Click in either location to move the caret there, then -click in the second location to add another caret. You are now editing in both locations, so all we have to do is type , EOFError to add it to both lines.

We asked for the feature and we got it!

Is it Really About the Mouse?

If you have read this far and you are like me, you should have one burning question: what made Allan give in? You might like answer: he realized that this feature wasn't just about the mouse. That means we got more than we bargained for with our request.

Some features in TextMate 2 have been rethought in terms of multiple carets and new features have been added with multiple carets in mind. These combine to create a powerful system which gives you many different ways to edit in multiple places at once, without needing to learn a ton of new tricks.

Jagged Columns

If you used TextMate 1 for any amount time, odds are good that you saw column selection in action. It was my weapon of choice when I wanted to show off some quick editing tricks.

TextMate 2 still has column selection, of course. It's really just a use of multiple carets now though and that dramatically increases the ways in which we can use it.

For example, let's say we have some lines of data that vary in length:

one
two
three
four
five
six
seven
eight
nine
ten

Now I can put my cursor at the front on all of those lines the same way I did in TextMate 1: move the caret before the o in one (⌘↑ if it's at the beginning of your document), press ⇧⌥↓ to drag a selection down in front of the t in ten (this is a column movement command), and tap to switch to column selection mode.

In TextMate 1, that gave you a special case selection that would always stay lined up vertically. But it TextMate 2, you can just think of it as ten different carets. That means we now have this:

‸one
‸two
‸three
‸four
‸five
‸six
‸seven
‸eight
‸nine
‸ten

But those carets may not stay in a perfect line if our editing actions require otherwise. To show that, let's move to the end of those lines (⌘→):

one‸
two‸
three‸
four‸
five‸
six‸
seven‸
eight‸
nine‸
ten‸

It's worth noting that getting a caret at the end of a group of selected lines was a separate feature in TextMate 1 called Edit Each Line in Selection (⌥⌘A). I loved that feature, but didn't need it often enough to remember the keyboard shortcut for it. I always went hunting through the menu. However, now that it's just another use of multiple carets and I can get to it using commands I am intimately familiar with, like column selection and the movement keys, I think it will be a lot more natural to pick up. For this reason, Edit Each Line in Selection was removed from TextMate 2.

Again, you can use most normal editing commands with multiple carets. Insert some text, delete text, move around, etc. If you use variable width commands like Select Word (⌃W), Select Enclosing Typing Pairs (⇧⌘B), Select Current Scope (⌃⌥B), any movement keys, forward delete () or backward delete (), Find Next (⌘G), or even something like Toggle Case of Character (⌃G), your carets may end up in different positions on different lines. That's why these commands can be so powerful when combined with multiple carets since they allow you to edit in terms of concepts like "words" or "scopes" which could mean something a little different for each caret.

An Aside, Literally

TextMate 1, had a special editing mode called Freehand Editing (⌥⌘E). It allowed you to place the caret beyond the boundaries of the data. If you edited in these margins, the gaps were filled in with spaces.

In TextMate 2, you don't really need to activate this mode due to some simple enhancements to caret placement. Column selection is immune to data boundaries, for example.

Thus, if I write the following Ruby method call, notice I forgot the commas after the arguments, and have my caret positioned as shown:

method( arg_one
        arg_forty_two‸
        arg_three )

the fastest fix is ⇧↑ to create a selection, to switch it into column mode, and type a , to enter it on both lines. This works because the top caret is in Freehand Mode. (Technically, this exact example also works in TextMate 1, but in TextMate 2 you can move the carets with the top one staying in Freehand Mode as needed.)

In TextMate 2, you can also make this correction using the mouse. An -click allows you to place a caret beyond data boundaries (in Freehand Mode). That works for multiple carets too, so you could just make an ⌥⌘-click above your first caret in the example above and type the ,.

Finding More Carets

There's another way to get multiple carets without ever leaving the keyboard and that's the new Find All feature.

If you need to do some tricky replacement, you can always bring up the Find dialog, craft a regular expression, and turn it loose on your data. That works, but it may require a few tries and a little use of Undo to get exactly what you need.

Find All is the other option. It's basically a live edit Find and Replace feature using the normal TextMate 2 editing tools. Oh, and you can totally skip the dialog if you like.

Ready for an example? What if we had some code doing logging in various places?

logger.info "Starting work..."
#...
if very_bad_stuff?
  logger.error "Uh guys, we should NEVER be here!"
end
#...
logger.debug "The current_user is:  #{current_user.id}."
#...
logger.info "Done."

Now let's say there is a decision to start using a different logger API. It doesn't have as rich of an interface, replacing methods like info(), error(), and debug() with just log(), but it is going to aggregate the messages from all of your servers onto a centralized box. It was decided that the tradeoff is worth it.

You can change the needed method calls with these steps. First, just select any occurrence of logger and push ⌘E to put it on the Find clipboard. Then trigger Find All (in Selection) with ⌥⌘F (it automatically works on the whole document if the current selection matches the clipboard). That should highlight all logger references in the document. Those selections are just multiple carets with some preselected content. Tap → twice to move over to the method calls and you should see this:

logger.‸info "Starting work..."
#...
if very_bad_stuff?
  logger.‸error "Uh guys, we should NEVER be here!"
end
#...
logger.‸debug "The current_user is:  #{current_user.id}."
#...
logger.‸info "Done."

From here the replacement is trivial. Select the methods with ⌃W and type log to replace them. Job done:

logger.log "Starting work..."
#...
if very_bad_stuff?
  logger.log "Uh guys, we should NEVER be here!"
end
#...
logger.log "The current_user is:  #{current_user.id}."
#...
logger.log "Done."

As I showed, Find All will match whatever is inside of a selection when you have one. If you don't though, it defaults to the entire document.

Copy and Paste Meets Multiple Carets

One question that's bound to arise is, can I Copy and Paste with multiple carets? Sure you can.

Let's go back to our list of numbers as an example. We will start with a many-to-many example. Say we want to break this list into two columns:

one
two
three
four
five
six
seven
eight
nine
ten

Do some -clicking to get a caret on every other line:

one
‸two
three
‸four
five
‸six
seven
‸eight
nine
‸ten

With that done, press ⌃W to grab the words, ⌘X for Cut, and to remove the extra lines. That leaves us with:

one‸
three‸
five‸
seven‸
nine‸

If we don't need even columns, we can just add a space and Paste (⌘V) right now.

For even columns, just -click a space or two after the three or seven. Then use that caret to line up four ⌥⌘-clicks. You should end up with:

one    ‸
three  ‸
five   ‸
seven  ‸
nine   ‸

A simple Paste (⌘V) finishes the job:

one    two
three  four
five   six
seven  eight
nine   ten

The one-to-many case works as well, by just repeating the content. Let's say we have some metric data:

12
 5
27
 2
--
46 "bugs"

but perhaps your pointy haired boss would prefer to see the word bugs on every line. Just double-click the word bugs and Copy (⌘C) it. Press ↑ twice to end up behind the 2, then ⇧↑ three times to drag a selection behind the numbers, and to switch into column mode. Type a space and Paste (⌘V). This pastes four copies of the clipboard contents, leaving you with:

12 bugs‸
 5 bugs‸
27 bugs‸
 2 bugs‸
--
46 "bugs"

Notice that we get to keep our multiple carets with each one placed after the pasted text. That's good, because I forgot to grab the quotes. We can add them with ⌃W to select the words and typing a single quote.

Finally, the many-to-one case collapses data as you would expect. So if we had Ruby data like:

name = { first: "James",
         middle: "Edward",
         last: "Gray",
         # leave the following out of dumb web forms
         suffix: "II" }

and we want to collapse that into one string, we could select the first colon, add it to the Find clipboard with ⌘E, switch the selection to the entire hash with ⇧⌘B, Find All with ⌥⌘F, then press → three times to move to the names. Now we have:

name = { first: "‸James",
         middle: "‸Edward",
         last: "‸Gray",
         # leave the following out of dumb web forms
         suffix: "‸II" }

From there select the names with ⌃W, Copy (⌘C) them, switch the selection back to the hash with ⇧⌘B, and replace it with the names using ⌘V. That gives us:

name = James‸Edward‸Gray‸II‸

We can finish off the string by typing a space to add it after each word, pressing ⌘→ to merge down to one caret at the end of the line, removing the extra space with , selecting the names by pushing ⇧⌥← four times, and typing a quote to enclose the string. That leaves:

name = "James Edward Gray II"

As you can see, all of these multiple caret features combine to create a powerful suite of editing tools. Thanks Allan, for knowing what we wanted more than we did.