oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Scripting Rectangular Selections in BBEdit 7

by John Gruber

Among the new features introduced by BBEdit 7.0, the most interesting is support for rectangular text selections.

It's a feature that has long been offered in many other text editors (mostly on other platforms), thus, it has been a frequent request from BBEdit users who've seen the feature elsewhere. Rectangular selections make it easier to work with tabular formatted text files. For example, you can easily delete the first 10 characters on every line in a file with one press of the Delete key.

To make a rectangular selection in BBEdit 7, hold down the Option key, then click and drag to select a rectangular region of text. The cursor will change from an I-beam to a plus sign as a visual indication that you are creating a rectangular selection. BBEdit only allows you to create rectangular selections when soft-wrapping is turned off in the front most window. Conversely, while there exists a rectangular selection in the front most window, BBEdit will not allow you to turn on soft-wrapping.

A screenshot says a thousand words:

Screenshot of a BBEdit rectangular selection.
Figure 1.

Support for rectangular selections isn't truly necessary to manipulate columns of text. You can instead use AppleScript or regular expressions (either via BBEdit's Find dialog, or for more complicated actions, with a Perl filter). For example, if you wanted to delete the 11th through 20th characters in every line of text file, you could use the following Perl filter:

#!/usr/bin/perl -wp

Which, of course, leaves out the vast majority of people who don't know regular expressions and don't know Perl or AppleScript. And even if you do know Perl, setting up a custom Perl filter each time you want to slice out a column of text is a bit of a hassle. You may not encounter text in such a format every day, but when you do, rectangular selections are handy indeed.

Once you've seen rectangular text selection in action, it seems both obvious and simple, so you might wonder why it took so long for BBEdit to support it. One reason is that it's not nearly as simple as it looks, especially when you look under the hood using AppleScript.

A regular text selection is just a contiguous range of characters. If you run a simple AppleScript like this:

tell application "BBEdit"
    get selection of window 1
end tell

The result will look something like this:

characters 182 thru 243 of text window 1 of application "BBEdit"

But a rectangular selection is not a contiguous range of characters. It looks contiguous on screen because the Nth characters on each line of text line up vertically. But a text file is not a matrix. It is simply a linear series of characters. To select a rectangle of characters, it is not enough for BBEdit to merely draw a rectangle on screen and select every character within the box.

This becomes obvious if you switch the display font from a monospaced typeface to a proportional one:

Screenshot of a 'rectangular' selection that doesn't look like a rectangle, because the typeface is not fixed-width.
Figure 2.

All of a sudden, the rectangular selection no longer looks like a rectangle. Depending on your choice of font and the width of the characters, it might not even be close. You can also create a non-rectangular, rectangular selection--even with a fixed-width typeface--by selecting characters at the end of lines that are not the same length. See Fig. 3:

Screenshot of a 'rectangular' selection that isn't a rectangle, because it includes lines of unequal length.
Figure 3.

Rectangular Selection Scripting Basics

What actually happens when you make a rectangular selection is that BBEdit creates multiple selections, one for each line of text in the rectangular range. You can see this by running the aforementioned get selection of window 1 script while the front window has a rectangular selection. The result, in AppleScript, will be a list that looks like this:

    characters 11 thru 20 of text window 1 of application "BBEdit", 
    characters 42 thru 51 of text window 1 of application "BBEdit", 
    characters 73 thru 82 of text window 1 of application "BBEdit", 
    characters 104 thru 113 of text window 1 of application "BBEdit", 
    characters 135 thru 144 of text window 1 of application "BBEdit", 
    characters 166 thru 175 of text window 1 of application "BBEdit"

The difference is that a normal BBEdit selection is a reference to a range of characters, but a rectangular selection is a list of character ranges, one for each line of the rectangular selection. Each element of this list is equivalent to a normal BBEdit selection range.

You can test whether the current selection is rectangular by checking its class. If it is a list, it's a rectangular selection; if it is a character (or an insertion point), it's a normal selection.

set the_sel to selection of window 1 of application "BBEdit"
if class of the_sel is list then
    display dialog "It's a rectangular selection."
    display dialog "It's a normal selection."
end if

You can access each element of a rectangular selection list just like a normal selection range. All you need to do is loop through the items in the selection list. Here's a script that will speak each line of a rectangular selection:

tell application "BBEdit"
    set the_sel to selection of window 1
    if class of the_sel is list then
        -- it's a rectangular selection
        repeat with s in the_sel
            say (s as text)
        end repeat
        -- normal (non-rectangular) text selection
        say (the_sel as text)
    end if
end tell

Another option for dealing with rectangular selections is to tell AppleScript to coerce them to text. If you use this line to get the selection:

set the_sel to selection of window 1 as text

you will always get the selection as a single text object. Each line of a rectangular selection will be delimited by a return. This is, in effect, the same thing that happens when you copy a rectangular selection, then paste it into a new text window.

Since BBEdit returns a rectangular selection in the form of a list when you get the selection, it would make sense that you could pass BBEdit a list if you want to set a rectangular selection. And indeed, that's how you do it. Here's a script that will create a rectangular selection consisting of the second and third characters on the first three lines of the front window.

tell application "BBEdit"
    tell window 1
        select {characters 2 thru 4 of line 1, ¬
            characters 2 thru 4 of line 2, ¬
            characters 2 thru 4 of line 3}
    end tell
end tell

Note that the script specifies characters 2 thru 4, but only characters 2 through 3 are actually selected. This is caused by a bug in BBEdit 7.0.1. It selects one character too few when you create a rectangular selection via the scripting interface. You can see this clearly by running the following:

tell window 1 of application "BBEdit"
    set s to selection
    select s
end tell

This script simply gets the current selection, then re-selects it. It should be a no-op,and it is, when you have a regular selection. But when you have a rectangular selection, the selection will shrink by one character width each time you run the script.

The bug has been reported to Bare Bones, but until it's fixed in a future version of BBEdit, you can work around it by specifying one extra character when creating rectangular selections via AppleScript. This workaround is not perfect, however: it will fail if by adding an extra character, you end up specifying a character offset that does not exist. For example, let's say you want to create a rectangular selection consisting of characters 3 through 4 of the first two lines. To compensate for this bug, you might try:

tell window 1 of application "BBEdit"
    select {characters 3 thru 5 of line 1, ¬
        characters 3 thru 5 of line 2}
end tell

This work in some cases, but the reference to characters 3 thru 5 will fail if any of the two lines are only four characters long. You can avoid this by avoiding line-relative character offsets, and instead using window-relative character offsets:

tell window 1 of application "BBEdit"
    select {characters 3 thru 5, ¬
        characters 8 thru 9}
end tell

Even this technique is not perfect, however -- it still will not allow you to create a rectangular selection that includes the last character of the window.

Pages: 1, 2

Next Pagearrow