oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Scripting Rectangular Selections in BBEdit 7
Pages: 1, 2

Unsupported: Non-Contiguous Selections

Now things start to get interesting because scripts that create rectangular selections work regardless if the front most BBEdit window is soft-wrapped.

Let's say you have a file with three lines of text. With soft-wrapping turned off, after running the above script, you see this:

Screen shot.
Figure 4.

But if you turn soft-wrapping on (before making the rectangular selection, since BBEdit won't allow you to toggle soft-wrapping if a rectangular selection already exists), then run the script, you see this:

Screen shot.
Figure 5.

The same characters are selected, but because soft-wrapping is on, they no longer form a rectangle. That's just it. They never really were a rectangle in the first place. They just look like a rectangle when soft-wrapping is off and the display font is a fixed-width typeface.

We already know that BBEdit's scripting interface deals with rectangular selections not as rectangular regions, but as lists of character ranges. It ends up that only BBEdit's graphical interface enforces the restriction that rectangular selections must be rectangular regions of text in non-soft-wrapped windows. The scripting interface has no such restrictions and will accept any list of character ranges, regardless if the window is soft-wrapped or if the character ranges form a rectangle.

In other words, via the scripting interface, BBEdit allows you to create multiple, non-contiguous text selections.

A word of caution, however. The only officially supported form of discontiguous selection in BBEdit 7.0 are the rectangular selections offered by the graphical interface. The following scripts work in BBEdit 7.0.1 but could very well break in the future if Bare Bones changes the scripting interface to enforce the same restrictions as the graphical interface. Until and if Bare Bones Software officially supports these techniques, they should not be counted on for production purposes.

Via the scripting interface, you can select ranges with different offsets on each line:

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

You can even select multiple ranges of characters on the same line:

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

Screen shot.
Figure 6.

If you tell BBEdit to select adjacent character ranges, BBEdit will merge them into a single regular selection range. If you run this script:

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

the return value is not a list, but instead a single character range: characters 1 thru 3 of text window 1 of application "BBEdit".

Select All Matches

Arbitrary multiple selections look cool, but are they useful? Other editors have offered multiple selections for a long time, but in my experience, most of them have been styled text editors or word processors--Nisus Writer, for example. Multiple selections have obvious utility in a styled editor or word processor. For example, you could select a bunch of discontiguous words, then change their font, color and size all at once.

That's not something you can do in a plain text editor like BBEdit. However, I can think of several ways that discontiguous selections can be put to use in BBEdit. For example, the following AppleScript will prompt you for a grep pattern, then select all matches of that pattern in the front window. Normally when you perform a "find all" in BBEdit, the results are displayed in a two-paned results browser, with a list of matches on top and the text of the document displayed underneath. With the following script, you can see all matches of the pattern selected at once.

property search_pat : ""

tell application "BBEdit"
   set w to text window 1
   set search_opts to {search mode:grep, starting at top: ¬
      true, case sensitive:false, returning results:true}
   set search_pat to text returned of (display dialog ¬
      "Select all text matching grep pattern:" default answer search_pat)
   set search_result to find search_pat searching in {text 1 of w} ¬
      options search_opts

   if found of search_result is true then
      set match_list to {}

      -- last_char is used in the bug workaround below
      set last_char to offset of last character of w

      repeat with cur_match in found matches of search_result
         set start_char to (start_offset of cur_match)
         set end_char to (end_offset of cur_match)

         -- start workaround for BBEdit 7.0.1 bug
         if end_char is less than (offset of last character of w) then
            set end_char to end_char + 1
         end if
         -- end workaround

         copy (characters start_char thru end_char of text window 1) ¬
            to end of match_list
      end repeat
      tell w to select match_list
      beep -- no matches
   end if
end tell

Here's how it works.

First, we create a property called search_pat. Properties are similar to normal AppleScript variables, but their values are remembered after the script finishes executing. Thus, each time you invoke the Select All Matches script, the search pattern will default to the string you last searched for.

Next we create a variable to refer to the front most BBEdit window (simply because it's so much shorter to type "w" than "text window 1"), set the search options, and prompt for the search pattern.

Then we tell BBEdit to perform the search. To perform a batch search in BBEdit via AppleScript, you need to pass a list as the search target. By putting "{text 1 of w}" inside curly braces, we create a list, albeit a list with a single element. If we had omitted the curly braces, BBEdit would have performed a normal search, finding only the first occurrence of the pattern.

Mac OS X Hints

Related Reading

Mac OS X Hints
The 500 Most Amazing Power Tips
By Rob Griffiths

Next we check the found property of the search result to see if the search was successful. If it was not, we beep and the script ends.

If the search was successful, we create an empty list named match_list. This is a list we'll construct with the character offsets of the text we want BBEdit to select. We also create a variable named last_char that holds the numeric offset of the last character in the window. last_char is used later on in the script as part of a workaround for the off-by-one selection bug mentioned earlier in this article.

Next we loop through each of the found matches. With each match, we copy the starting and ending offsets. As a workaround for the off-by-one bug, we add 1 to the ending offset, but only if doing so won't refer to an offset larger than that of the last character in the window. (We cache the value of the last character's offset in the last_char variable as a small performance tweak, referring to "offset of last character of w" each time through the repeat loop incurs a small amount of overhead.)

At the end of the repeat loop, we copy a reference to the matching characters to the match_list variable. It is essential to use the copy reference to list syntax to append each reference to the list, rather than using AppleScript's "&" concatenation operator. If we had instead used something like this:

set match_list to match_list & (characters start_char thru ¬
	end_char of text window 1)

we would have created a list of text strings, such as {"One", "Two", "Three"}, rather than a list of references to text objects. See Apple's developer documentation regarding AppleScript lists for more details.

Finally, after the repeat loop finishes, we tell BBEdit to select the elements of match_list, and we're done.

John Gruber is a freelance writer, Web developer, designer, and Mac nerd. He combines those interests on his Web site, Daring Fireball.

Return to the Mac DevCenter.